# Tesseract.js Admin & Developer Guide

This guide is for engineers integrating Tesseract.js at scale, operators running it in production (especially long-lived Node.js services), and contributors who need to understand the codebase deeply. It covers architecture, configuration, security, operations, the build/release pipeline, a module-by-module internals reference, and improvement opportunities.

For end-user task instructions (how to recognize an image, output formats, recipes), see the User Guide.

> [!NOTE]
> This guide documents **Tesseract.js v7.0.0** (package `tesseract.js`, Apache-2.0). It reflects the source at commit `a1ca80d`.

---

## Table of Contents

1. [Executive Summary](#executive-summary)
2. [Architecture](#architecture)
3. [Repository Structure](#repository-structure)
4. [System Requirements](#system-requirements)
5. [Configuration Reference](#configuration-reference)
6. [Module Reference](#module-reference)
7. [Recognition Pipeline Internals](#recognition-pipeline-internals)
8. [Caching Subsystem](#caching-subsystem)
9. [Security Analysis](#security-analysis)
10. [Operations](#operations)
11. [Build & Release Pipeline](#build--release-pipeline)
12. [Testing Strategy](#testing-strategy)
13. [Code Quality & Conventions](#code-quality--conventions)
14. [Version Migration](#version-migration)
15. [Potential Improvements](#potential-improvements)
16. [Quick Reference](#quick-reference)

---

## Executive Summary

Tesseract.js is a **wrapper library**, not an OCR engine. It brings the C++ [Tesseract](https://github.com/tesseract-ocr/tesseract) OCR engine to JavaScript by orchestrating a WebAssembly build of that engine ([tesseract.js-core](https://github.com/naptha/tesseract.js-core)). The library's own code is small (~72 KB of source across ~52 code files); its job is **transport, environment abstraction, resource management, and a clean async API** — not recognition logic.

Key architectural facts an operator should internalize:

- **Two-thread model.** Every "worker" runs the heavy engine off the main thread — a Web Worker in browsers, a `worker_threads` `Worker` in Node.js. All communication is message-passing.
- **Runtime asset loading.** The WASM core and language `.traineddata` are downloaded from a CDN (jsDelivr by default) at runtime, then cached (IndexedDB in browsers, filesystem in Node.js). Nothing is bundled.
- **Build selection by capability.** At load time the worker detects SIMD / relaxed-SIMD support and whether the Legacy model is needed, then loads the matching core build for best performance.
- **Memory is the operational risk.** WASM memory only grows; workers accumulate dictionary state. Long-running services must recycle workers.

| Property | Value |
|---|---|
| Package | `tesseract.js` |
| Version | 7.0.0 |
| License | Apache-2.0 |
| Module type | CommonJS (`"type": "commonjs"`) |
| Main entry | `src/index.js` |
| Type definitions | `src/index.d.ts` |
| CDN entry (UMD) | `dist/tesseract.min.js` |
| Runtime dependencies | 8 (see [Dependencies](#runtime-dependencies)) |
| Engine | `tesseract.js-core` (separate WASM project) |

---

## Architecture

Tesseract.js cleanly separates a **main-thread API layer** from a **worker-thread execution layer**, with **environment adapters** that swap browser vs. Node.js implementations behind identical interfaces.

```text
┌──────────────────────────── MAIN THREAD ────────────────────────────┐
│  src/index.js  (public exports)                                      │
│    ├── createWorker  ── src/createWorker.js                          │
│    │      └── builds worker handle, manages promises, paths, OEM     │
│    ├── createScheduler ─ src/createScheduler.js                      │
│    │      └── worker pool + job queue (round-robin to idle workers)  │
│    ├── Tesseract.recognize/detect (deprecated one-shots)            │
│    ├── OEM / PSM / languages constants                               │
│    └── createJob, utils/{getId,log,resolvePaths,getEnvironment}     │
│                                                                      │
│  src/worker/{browser,node}/  ── environment ADAPTER (main side)      │
│    spawnWorker · terminateWorker · send · onMessage · loadImage      │
└───────────────────────────────┬──────────────────────────────────┘
                                 │  postMessage(packet) / onmessage
                                 ▼
┌────────────────────────── WORKER THREAD ────────────────────────────┐
│  src/worker-script/index.js  (action dispatcher)                     │
│    handlers: load · FS · loadLanguage · initialize · setParameters  │
│              · recognize · detect · terminate                        │
│    setAdapter({ getCore, gunzip, fetch, readCache/writeCache/... })  │
│                                                                      │
│  src/worker-script/{browser,node}/  ── environment ADAPTER (worker)  │
│    getCore (SIMD-aware WASM loader) · gunzip · cache (IndexedDB|fs)  │
│  src/worker-script/utils/  setImage · dump · arrayBufferToBase64     │
│                                 │                                    │
│                                 ▼                                    │
│              tesseract.js-core  (WebAssembly Tesseract engine)        │
└──────────────────────────────────────────────────────────────────┘
```

### Design patterns

**Adapter pattern (the central idea).** The same worker-script core (`src/worker-script/index.js`) runs in both browser and Node.js. It calls `setAdapter(...)` to receive environment-specific implementations of `getCore`, `gunzip`, `fetch`, and the cache methods. The browser adapter (`src/worker-script/browser/`) uses `importScripts`, `zlibjs`, and `idb-keyval`; the Node adapter (`src/worker-script/node/`) uses `require`, `zlib`, and `fs`. The recognition logic is written once.

**Symmetric main-side adapter.** Likewise, `src/worker/browser/` and `src/worker/node/` expose identical `spawnWorker`, `terminateWorker`, `send`, `onMessage`, `loadImage` modules. `createWorker` imports from `./worker/node` and relies on the `browser` field in `package.json` to swap to the browser build at bundle time.

**Job/promise correlation.** Each call from the main thread creates a job (`createJob`) with an id and an `action`. `createWorker` stores `{resolve, reject}` keyed by `` `${action}-${jobId}` `` and resolves it when a matching message returns. This lets multiple in-flight jobs coexist over a single message channel.

**Command dispatch.** The worker script maps each `packet.action` to a handler function in a lookup object (`{ load, FS, loadLanguage, initialize, setParameters, recognize, detect, terminate }[packet.action]`).

> [!NOTE]
> The `browser` field in `package.json` (`"./src/worker/node/index.js": "./src/worker/browser/index.js"`) is what makes a single `require('./worker/node')` resolve to the browser implementation when bundled for the web. This is the seam that makes the dual-environment design work without conditional `require`s in the hot path.

---

## Repository Structure

```text
tesseract.js/
├── src/                          Core library (the documented surface)
│   ├── index.js                  Public exports
│   ├── index.d.ts                TypeScript definitions (full type surface)
│   ├── Tesseract.js              Deprecated one-shot recognize()/detect()
│   ├── createWorker.js           Main-thread worker factory & job manager
│   ├── createScheduler.js        Worker pool + job queue
│   ├── createJob.js              Job object factory
│   ├── constants/                OEM, PSM, imageType, defaultOptions, languages
│   ├── utils/                    getEnvironment, getId, log, resolvePaths
│   ├── worker/                   Main-side adapters
│   │   ├── browser/              spawnWorker (Blob URL), loadImage (DOM/Blob), ...
│   │   └── node/                 spawnWorker (worker_threads), loadImage (fs/fetch)
│   └── worker-script/            Worker-side execution
│       ├── index.js              Action dispatcher + all OCR handlers
│       ├── constants/            defaultOutput
│       ├── utils/                setImage, dump, arrayBufferToBase64
│       ├── browser/              getCore (importScripts), gunzip (zlibjs), cache (idb-keyval)
│       └── node/                 getCore (require), gunzip (zlib), cache (fs)
├── examples/                     Official runnable examples (browser + node)
├── benchmarks/                   Speed & memory benchmarks (browser + node)
├── docs/                         Authoritative user docs (api, faq, performance, ...)
├── scripts/                      Dev server, webpack & rollup build configs
├── tests/                        Mocha + Karma test suites + assets
├── package.json                  Metadata, scripts, deps, browser field
├── babel.config.json             @babel/preset-env, "last 2 versions"
└── karma.conf.js                 Browser test runner (Chrome + Firefox headless)
```

---

## System Requirements

| Layer | Requirement |
|---|---|
| Node.js (v7) | **v16+** (v6 needs v14+) |
| Browser | JavaScript **and** WebAssembly support |
| CPU features | SIMD / relaxed-SIMD are auto-detected and used when present; a non-SIMD core is the fallback |
| Network (default) | Outbound HTTPS to jsDelivr for the core and to the tessdata CDN for language files |
| Memory | High — each worker holds a full WASM engine instance; budget hundreds of MB per active worker for large images |
| Storage | Cache for `.traineddata` (IndexedDB in browser, filesystem in Node.js) |

> [!IMPORTANT]
> **React Native is not supported** because it lacks WebAssembly. Other constrained runtimes (some serverless "edge worker" platforms, certain browser-extension manifest setups) may require explicit `workerPath`/`corePath` configuration or may not work at all.

---

## Configuration Reference

All configuration is supplied programmatically — **there are no config files, environment variables, or `.env` settings** in Tesseract.js. Configuration flows through three channels.

### 1. `createWorker` arguments

```javascript
createWorker(langs = 'eng', oem = OEM.LSTM_ONLY, options = {}, config = {})
```

### 2. WorkerOptions (third argument)

| Option | Type | Default | Notes |
|---|---|---|---|
| `corePath` | string | jsDelivr `tesseract.js-core@v<coreVersion>` | **Must be a directory** holding all four core builds |
| `langPath` | string | jsDelivr `@tesseract.js-data/<lang>/4.0.0` (or `…/4.0.0_best_int` for LSTM-only) | No trailing `/` |
| `workerPath` | string | jsDelivr `tesseract.js@v<version>/dist/worker.min.js` (browser); local path (node) | Override for bundler/framework issues |
| `dataPath` | string | `.` | Path inside the WASM MEMFS for traineddata; rarely changed |
| `cachePath` | string | `.` | Node: cache folder; browser: IndexedDB key prefix |
| `cacheMethod` | string | `write` | `write` / `readOnly` / `refresh` / `none` |
| `legacyCore` | boolean | `false` | Download code supporting the Legacy model |
| `legacyLang` | boolean | `false` | Download data supporting the Legacy model |
| `workerBlobURL` | boolean | `true` | Wrap the worker script in a Blob URL (browser) |
| `gzip` | boolean | `true` | Whether remote traineddata is gzipped |
| `logger` | function | `() => {}` | Progress callback |
| `errorHandler` | function | — | Worker error callback |

> [!NOTE]
> `defaultOptions` (`src/constants/defaultOptions.js`) sets only `workerBlobURL: true` and a no-op `logger`. Environment-specific `defaultOptions` add the appropriate `workerPath` (node: a filesystem path under `worker-script/node/index.js`; browser: the CDN `worker.min.js` for the current version).

**`cacheMethod` semantics (from source):**

| Value | Reads cache | Writes cache | Cache-delete on init failure |
|---|---|---|---|
| `write` (default) | Yes | Yes | Yes |
| `readOnly` | Yes | No | No |
| `refresh` | No | Yes | Yes |
| `none` | No | No | No |

The read path is skipped when `cacheMethod ∈ {refresh, none}`; the write path runs when `cacheMethod ∈ {write, refresh, undefined}`. On initialization failure, cached data is deleted only when `cacheMethod ∈ {write, refresh, undefined}` (i.e. when we have write permission).

### 3. Tesseract engine parameters

Two sub-channels:

- **Runtime parameters** — via `worker.setParameters({...})` or per-call `recognize` options. Applied with `api.SetVariable(key, value)`. Keys starting with `tessjs_` are filtered out (reserved for Tesseract.js internals).
- **Init-only parameters** — via the `config` argument of `createWorker`/`reinitialize`. These map to Tesseract's `[type]_INIT_MEMBER` settings and cannot be changed after `Init`. Examples: `load_system_dawg`, `load_freq_dawg`, `load_unambig_dawg`, `load_punc_dawg`, `load_number_dawg`, `load_bigram_dawg`, `tessedit_ocr_engine_mode`, `tessedit_init_config_only`, `language_model_ngram_on`, `language_model_use_sigmoidal_certainty`.

> [!WARNING]
> Calling `setParameters` with an init-only key has no effect and logs `Attempted to set parameters that can only be set during initialization: <names>`. The engine silently ignores them — there is no exception, so watch the console during integration.

The `config` argument may be either a config-file string or an object. If an object, it is serialized to Tesseract config-file text (`JSON.stringify` then `,`→newline, `:`→space, quotes/braces stripped) and written to `/config` in the WASM FS before `Init`.

---

## Module Reference

### Main thread

| Module | Exports | Responsibility |
|---|---|---|
| `src/index.js` | `createWorker`, `createScheduler`, `recognize`, `detect`, `setLogging`, `OEM`, `PSM`, `languages` | Public API surface |
| `src/createWorker.js` | factory `(langs, oem, options, config) => Promise<Worker>` | Spawn worker, resolve paths, manage per-job promises, drive the `load → loadLanguage → initialize` boot sequence, expose worker methods |
| `src/createScheduler.js` | factory `() => Scheduler` | Maintain `workers`, `runningWorkers`, `jobQueue`; `dequeue` assigns the next queued job to an idle worker |
| `src/createJob.js` | factory `({id, action, payload}) => job` | Generate job ids (`getId('Job', n)`) and normalize job objects |
| `src/Tesseract.js` | `recognize`, `detect` | Deprecated one-shots: create a worker, run one job, `terminate` in a `finally` |

### Constants

| File | Contents |
|---|---|
| `src/constants/OEM.js` | `TESSERACT_ONLY:0`, `LSTM_ONLY:1`, `TESSERACT_LSTM_COMBINED:2`, `DEFAULT:3` |
| `src/constants/PSM.js` | 14 page-segmentation modes (`'0'`–`'13'`) |
| `src/constants/imageType.js` | `COLOR:0`, `GREY:1`, `BINARY:2` |
| `src/constants/defaultOptions.js` | `workerBlobURL:true`, no-op `logger` |
| `src/constants/languages.js` | ~90 symbolic language-code constants |
| `src/worker-script/constants/defaultOutput.js` | Default output flags — only `text:true`; all others `false` |

### Utilities

| File | Purpose |
|---|---|
| `src/utils/getEnvironment.js` | Detect runtime: `webworker` / `browser` / `node` (by globals `WorkerGlobalScope`, `document`, `process`) |
| `src/utils/getId.js` | `` `${prefix}-${cnt}-${random}` `` id generator |
| `src/utils/log.js` | Global `logging` flag, `setLogging`, gated `log()` |
| `src/utils/resolvePaths.js` | Resolve `corePath`/`workerPath`/`langPath` to absolute URLs in the browser (`new URL(s, location.href)`); identity in Node.js |

### Main-side adapters (`src/worker/{browser,node}/`)

| Module | Browser | Node.js |
|---|---|---|
| `spawnWorker` | `new Worker(URL.createObjectURL(blob))` wrapping `importScripts`, or direct `new Worker(workerPath)` | `new Worker(workerPath)` from `worker_threads` |
| `terminateWorker` | `worker.terminate()` | `worker.terminate()` |
| `send` | `worker.postMessage(packet)` | `worker.postMessage(packet)` |
| `onMessage` | `worker.onmessage = …` | `worker.on('message', …)` |
| `loadImage` | DOM-aware: URL/base64, `File`/`Blob`, `img`/`video`/`canvas`, `OffscreenCanvas` → `Uint8Array` | `fs`/`fetch`/base64 → `Uint8Array` |
| `defaultOptions` | adds CDN `workerPath` for current version | adds filesystem `workerPath` |

### Worker-side execution

| Module | Purpose |
|---|---|
| `src/worker-script/index.js` | The engine driver. Implements `load`, `FS`, `loadLanguage`, `initialize`, `setParameters`, `recognize`, `detect`, `terminate`; `dispatchHandlers` routes packets; `setAdapter` injects env code |
| `src/worker-script/utils/setImage.js` | Writes the image into the WASM FS; converts BMP via `bmp-js` (Leptonica BMP gaps); reads EXIF orientation; `api.SetImageFile(exif, angle)` (throws `Error attempting to read image.` on failure) |
| `src/worker-script/utils/dump.js` | Builds the result JSON: pulls each requested output (`GetUTF8Text`, `GetHOCRText`, `GetTSVText`, …), renders PDF via `TessPDFRenderer`, extracts pre-processed images, maps PSM/OEM enums back to strings |
| `src/worker-script/utils/arrayBufferToBase64.js` | Pure-JS base64 encoder for image output |
| `src/worker-script/{browser,node}/getCore.js` | SIMD-aware loader that selects the correct `tesseract.js-core` build |
| `src/worker-script/{browser,node}/gunzip.js` | `zlibjs.gunzipSync` (browser) / `zlib.gunzipSync` (node) |
| `src/worker-script/{browser,node}/cache.js` | `idb-keyval` (browser) / `fs` (node) cache CRUD |

---

## Recognition Pipeline Internals

What actually happens during `worker.recognize(image, options, output)`:

1. **Image load (main thread).** `loadImage` normalizes the input (URL, base64, Buffer, DOM element, File/Blob, local path) into a `Uint8Array`, then sends a `recognize` job to the worker.
2. **Option splitting.** In the worker, keys that are neither Tesseract.js options (`rectangle`, `pdfTitle`, `pdfTextOnly`, `rotateAuto`, `rotateRadians`) nor `tessjs_*` are treated as engine parameters and applied with `SetVariable` (after `SaveParameters`, restored with `RestoreParameters` afterward).
3. **Output planning.** `processOutput` merges requested formats over `defaultOutput` and counts how many require OCR. Outputs `imageColor`, `imageGrey`, `imageBinary`, `layoutBlocks`, `debug` do **not** require recognition; if only those are requested, `skipRecognition` is true and full OCR is bypassed.
4. **Image placement & rotation.** With `rotateAuto`, the engine temporarily switches to an auto PSM, finds lines, computes the skew angle (`GetGradient` or legacy `GetAngle`), and re-sets the image at that angle (angles < 0.005 rad are ignored). Otherwise `rotateRadians` (default 0) is used.
5. **Region selection.** If `rectangle` is given, `api.SetRectangle(left, top, width, height)`.
6. **Recognition.** `api.Recognize(null)` runs unless skipped; for layout-only requests, `api.AnalyseLayout()` runs instead.
7. **Result dump.** `dump` reads every requested format into a JSON object, appends `confidence`, `psm`, `oem`, `version`, and `rotateRadians`, then the result is posted back and the matching promise resolves.

> [!NOTE]
> Progress is reported throughout via `res.progress(...)`. The engine's internal `TesseractProgress(percent)` callback is rescaled to `(percent - 30) / 70` so the user-visible "recognizing text" progress runs 0→1 over the OCR phase.

---

## Caching Subsystem

Language data is the largest runtime download (typically ~2 MB; worst case, e.g. Legacy + Chinese Simplified, ~20 MB), so caching is central to performance.

**Flow (`loadLanguage` in `src/worker-script/index.js`):**

1. For each language, attempt a cache read of `<cachePath>/<lang>.traineddata` (skipped if `cacheMethod ∈ {refresh, none}`).
2. On a miss, fetch from `langPath` (or the default CDN). In Node.js, `langPath` may be a URL **or** a local path — `is-url` plus scheme checks (`file://`, `moz-extension://`, `chrome-extension://`) decide which. URLs are fetched (`.gz` appended when `gzip`); local paths are read from disk.
3. Detect gzip via magic bytes (`0x1F 0x8B`) and gunzip if needed.
4. Write the data into the WASM MEMFS (`<dataPath>/<lang>.traineddata`).
5. Write back to the cache when `cacheMethod ∈ {write, refresh, undefined}`.

**Self-healing on init failure.** If `api.Init` returns `-1` and data came from cache, the worker assumes the cached data may be bad or lack the requested OEM. It deletes the cache entry; if the failure mentions "components are not present", it re-runs `loadLanguage` (forcing a fresh download) and retries `Init` once.

| Environment | Cache backend |
|---|---|
| Browser | IndexedDB via `idb-keyval` (`cachePath` becomes a key prefix) |
| Node.js | Filesystem via `fs` (`cachePath` is a folder; default is the current working directory) |

> [!TIP]
> In Node.js, set `cachePath` to a stable, writable directory (e.g. a mounted volume) so cached traineddata survives container restarts and isn't written to the process CWD unpredictably.

---

## Security Analysis

Tesseract.js is a client/embedded library, so its threat surface differs from a server application. Findings below are based on the source.

### Network & supply chain

- **Default remote loading.** By default, the WASM core and language data are loaded from the jsDelivr CDN over HTTPS at runtime. This is a runtime dependency on a third-party CDN and an implicit trust of its contents.

> [!IMPORTANT]
> For high-assurance or air-gapped deployments, **self-host** the core (`corePath`), worker script (`workerPath`), and language data (`langPath`) from infrastructure you control, and pin versions. This removes the runtime CDN dependency and lets you verify artifacts. See [Operations](#operations).

- **`importScripts` of a remote URL (browser).** `getCore` calls `global.importScripts(corePathImportFile)` against the (possibly CDN) core URL — remote code execution by design. Whoever controls `corePath` controls executed code. Treat `corePath`/`workerPath` as security-sensitive configuration; never set them from untrusted user input.
- **Blob-URL worker (browser).** `spawnWorker` builds a worker from `new Blob(['importScripts("${workerPath}");'])`. Sites with a strict Content-Security-Policy must allow `worker-src blob:` (and the CDN in `script-src`) or set `workerBlobURL: false` and serve a same-origin `worker.min.js`.

### Input handling

- **Image bytes flow into a native (WASM) engine.** Untrusted images are parsed by Leptonica/Tesseract inside WebAssembly. WASM provides memory-sandboxing (no host memory access), which substantially contains parser bugs — but denial-of-service via pathological images (memory growth, long runtimes) is still possible. Bound input sizes and apply timeouts in server contexts.
- **BMP special-case.** `setImage` runs BMP files through `bmp-js` before handing them to Leptonica (which supports only some BMPs). This is extra attack surface in pure JS, though again sandbox-contained.
- **No code-injection from parameters.** Engine parameters go through `api.SetVariable`; the `config` object is serialized to a Tesseract config file, not evaluated. Keys are filtered (`tessjs_*` removed). There is no `eval` of user data.

### Filesystem (Node.js)

- The Node cache adapter reads/writes paths derived from `cachePath`/`langPath`. If those are ever set from untrusted input, path traversal is conceivable — keep them operator-controlled. `deleteCache` swallows errors by design (cache cleanup is best-effort).

### Secrets

- The library handles **no credentials, tokens, or secrets**. There is no authentication, no outbound telemetry, and no secret material in the codebase.

> [!CAUTION]
> The `postinstall` script runs `opencollective-postinstall || true` (a funding banner). It is harmless and non-fatal, but some environments dislike postinstall scripts. Issue #1030 tracks a request to remove it for Deno compatibility. Install with `--ignore-scripts` if your policy forbids postinstall hooks.

---

## Operations

### Running in long-lived Node.js services

> [!WARNING]
> **Recycle workers periodically.** WebAssembly memory only ever grows, so a single large image permanently raises a worker's footprint. Additionally, workers "learn" by adding encountered words to an internal dictionary; over thousands of unrelated documents this bloats with typos. Terminate and recreate workers/schedulers on a cadence — e.g. every ~500 jobs.

### Recommended production pattern

```javascript
const { createScheduler, createWorker } = require('tesseract.js');

const POOL_SIZE = 4;
const MAX_JOBS = 500;
let scheduler;
let jobsSinceReset = 0;

async function buildScheduler() {
  const s = createScheduler();
  await Promise.all(Array.from({ length: POOL_SIZE }, async () => {
    const w = await createWorker('eng', 1, { cachePath: '/var/cache/tessdata' });
    s.addWorker(w);
  }));
  return s;
}

async function recognize(image) {
  if (!scheduler || jobsSinceReset >= MAX_JOBS) {
    const old = scheduler;
    scheduler = await buildScheduler();
    jobsSinceReset = 0;
    if (old) await old.terminate(); // drain/replace
  }
  jobsSinceReset += 1;
  const { data: { text } } = await scheduler.addJob('recognize', image);
  return text;
}
```

> [!TIP]
> Size `POOL_SIZE` to available memory, not CPU count — each worker is memory-heavy. Start small (2–4) and load-test.

### Operational checklist

- [ ] Pin `tesseract.js` and `tesseract.js-core` to exact versions.
- [ ] Self-host `corePath`, `workerPath`, `langPath` (or accept the CDN dependency knowingly).
- [ ] Set a stable, writable `cachePath` (Node.js) on a persistent volume.
- [ ] Bound image size and add per-job timeouts.
- [ ] Recycle workers/schedulers on a job-count or time cadence.
- [ ] Set `oem`/`psm` explicitly if you need parity with the Tesseract CLI.
- [ ] For browsers, configure CSP for `blob:` workers and the CDN, or serve a same-origin worker.

### Observability

Enable progress/diagnostic logging with a `logger` callback or globally with `setLogging(true)`:

```javascript
const { setLogging } = require('tesseract.js');
setLogging(true); // verbose internal logs to console
```

There is no built-in metrics export; instrument around `addJob`/`recognize` (latency, success/empty/error counts) yourself.

---

## Build & Release Pipeline

The `dist/` artifacts are **not** committed; they are produced by the build. Source is authored as CommonJS and transpiled with Babel (`@babel/preset-env`, target "last 2 versions").

### npm scripts (`package.json`)

| Script | Command | Purpose |
|---|---|---|
| `start` | `node scripts/server.js` | Dev server on port 3000 (webpack-dev-middleware, writes to disk) |
| `build` | `rimraf dist && webpack --config scripts/webpack.config.prod.js && rollup -c scripts/rollup.esm.mjs` | Build UMD (webpack) + ESM (rollup) bundles into `dist/` |
| `profile:tesseract` | `webpack-bundle-analyzer dist/tesseract-stats.json` | Inspect main bundle size |
| `profile:worker` | `webpack-bundle-analyzer dist/worker-stats.json` | Inspect worker bundle size |
| `prepublishOnly` | `npm run build` | Ensure a fresh build before publish |
| `wait` | `rimraf dist && wait-on http://localhost:3000/dist/tesseract.min.js` | Wait for dev build before tests |
| `test` | `npm-run-all -p -r start test:all` | Start server and run the full suite |
| `test:all` | `npm-run-all wait test:browser test:node:all` | Browser + Node tests |
| `test:browser` | `karma start karma.conf.js` | Headless Chrome + Firefox |
| `test:node` / `test:node:all` | `nyc mocha …` | Node tests with coverage |
| `lint` / `lint:fix` | `eslint src` | Airbnb-base lint |

### Dev server (`scripts/server.js`)

An Express server that serves the webpack-compiled bundles and the repo statically on port 3000. It sets `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` — these COOP/COEP headers enable `performance.measureUserAgentSpecificMemory()` used by the benchmarks (and are required for SharedArrayBuffer-style isolation generally). The default dev page is `examples/browser/basic-efficient.html`.

> [!NOTE]
> The COOP/COEP headers are needed only for the benchmark memory measurements; the docs note they can be removed if problematic in other contexts.

### Contributor workflow

```shell
git clone https://github.com/naptha/tesseract.js.git
cd tesseract.js
npm install
npm start   # dev server with live rebuild of tesseract.min.js / worker.min.js
```

Before opening a PR:

```shell
npm run lint
npm run test
```

> [!IMPORTANT]
> Always confirm the automated tests pass before submitting a pull request. The dev server rebuilds `tesseract.min.js` and `worker.min.js` automatically when files in `src/` change.

---

## Testing Strategy

| Layer | Tooling | Location |
|---|---|---|
| Browser | Karma + Mocha, headless Chrome & Firefox | `karma.conf.js`, `tests/*.test.mjs` |
| Node.js | Mocha + nyc (coverage), `expect.js` | `tests/*.test.mjs` |
| Assertions | `expect.js` | — |

Test suites cover the FS interface, OSD detection, recognition, and the scheduler (`tests/FS.test.mjs`, `detect.test.mjs`, `recognize.test.mjs`, `scheduler.test.mjs`). Test assets include sample images in many formats and `.traineddata` for `eng`, `chi_tra`, and `osd`. The Karma config serves the four SIMD/relaxed-SIMD core builds and proxies them so browser tests exercise the real WASM core.

> [!TIP]
> Because browser tests need the built `dist/` bundles, the `test` script orchestrates the dev server (`start`), waits for the bundle (`wait`), then runs browser and node suites in parallel via `npm-run-all`.

---

## Code Quality & Conventions

- **Style:** ESLint with `eslint-config-airbnb-base`. Lint runs on `src` only.
- **Module format:** CommonJS throughout `src/` (`'use strict'` headers, `module.exports`). Migration to ESM is a tracked aspiration (issue #890) but not done.
- **Environment abstraction:** Strong, consistent adapter boundaries (`worker/` and `worker-script/` each have parallel `browser/`/`node/` trees with identical interfaces). This is the codebase's defining quality and makes it easy to reason about.
- **Small, single-purpose modules:** Most files are tiny (the largest single source file is the worker-script dispatcher at ~19 KB). Utilities are one function each.
- **Type coverage:** A complete hand-written `index.d.ts` describes the full public surface, including the deep `Page`/`Block`/`Paragraph`/`Line`/`Word`/`Symbol` result tree.
- **Backward-compatibility care:** The code carries explicit compatibility shims (e.g. `GetGradient` vs. legacy `GetAngle`; `TesseractCore` vs. older `TesseractCoreWASM`; tolerating `corePath` pointing at a single `.js` file). Comments explain *why*, which aids maintenance.

> [!NOTE]
> A reader auditing this codebase should start at `src/worker-script/index.js` (all OCR behavior) and `src/createWorker.js` (all main-thread orchestration). Everything else is adapters, constants, and small utilities.

---

## Version Migration

Highlights of recent breaking changes (full lists are in the linked upstream issues).

### v6 (from v5)

- **All output formats except `text` are disabled by default.** Re-enable explicitly, e.g. `worker.recognize(image, {}, { hocr: true })`.
- Minor changes to the structure of the `blocks` output.
- Memory leak fixed; overall runtime and memory reductions.
- Tracking: [issue #993](https://github.com/naptha/tesseract.js/issues/993).

### v5 (from v2–v4)

- `createWorker` arguments changed — language and OEM are now set in `createWorker` (e.g. `createWorker('chi_sim', 1)`).
- `worker.initialize` and `worker.loadLanguage` were removed — delete them from your code (workers come pre-loaded).
- ~50%+ smaller default downloads and lower memory use.
- Tracking: [issue #820](https://github.com/naptha/tesseract.js/issues/820); v2→v5 guide: [issue #771](https://github.com/naptha/tesseract.js/issues/771).

### v4

- Added rotation preprocessing (including auto-rotate); processed images retrievable; better scheduler/parallel support.
- `createWorker` became async; `getPDF` replaced by the `pdf` recognize option.

> [!IMPORTANT]
> The single most common migration break is the v6 output-format change. If a previously-working integration suddenly returns `null` for `hocr`/`tsv`/`blocks`, enable those formats in the third argument of `recognize`.

---

## Potential Improvements

Observations from the source and the issue tracker, framed as actionable opportunities for maintainers/integrators.

- **ESM migration (issue #890).** The library is CommonJS with a separate rollup ESM bundle step. A native-ESM source tree would simplify the build and improve tree-shaking and modern-runtime support (Deno, Bun).
- **Remove the `opencollective-postinstall` hook (issue #1030).** It complicates Deno installs and triggers postinstall-policy warnings; the funding banner could move elsewhere.
- **Non-standard runtime support.** Tracked gaps include Bun + Next.js API routes (#1064), Cloudflare Workers (#950), Electron node-mode (#1043), and browser-extension MV3 (#961). Most stem from build systems relocating the worker script — a more robust worker-path resolution strategy would reduce the recurring "Cannot find module" class of issues (#868-style).
- **`worker.spawnWorker` error propagation (issue #712).** Errors thrown during worker spawn are hard to catch from the caller; surfacing them through the returned promise would improve DX.
- **Custom traineddata via non-URL paths / blob URLs (issues #998, #965).** Loading custom language data from arbitrary locations has rough edges worth smoothing.
- **`languages` export on the default export (issue #887).** A small consistency fix to the public surface.
- **Documented "fast" language data tradeoffs.** The performance docs invite users to try `4.0.0_fast` data but note it is unbenchmarked; publishing quality/speed numbers would help operators make the call.

> [!NOTE]
> None of these are correctness bugs in the wrapper's core path — recognition itself is delegated to the upstream engine. They are integration-surface and build-system refinements.

---

## Quick Reference

### Public API

```text
createWorker(langs='eng', oem=1, options={}, config={}) -> Promise<Worker>
createScheduler() -> Scheduler
recognize(image, langs?, options?)  // deprecated
detect(image, options?)             // deprecated
setLogging(boolean)
OEM, PSM, languages
```

### Worker

```text
recognize(image, options?, output?, jobId?)
detect(image, jobId?)            // needs legacyCore+legacyLang
setParameters(params, jobId?)
reinitialize(langs?, oem?, config?, jobId?)
writeText / readText / removeFile / FS
terminate(jobId?)
```

### Scheduler

```text
addWorker(worker) -> id
addJob('recognize'|'detect', ...payload) -> Promise
getQueueLen() -> number
getNumWorkers() -> number
terminate() -> Promise
```

### Defaults

| Setting | Default |
|---|---|
| `langs` | `'eng'` |
| `oem` | `1` (LSTM_ONLY) |
| `psm` | `6` (SINGLE_BLOCK) |
| output | `text` only |
| `cacheMethod` | `write` |
| `gzip` | `true` |
| `workerBlobURL` | `true` |

### Runtime dependencies

| Package | Role |
|---|---|
| `tesseract.js-core` | The WebAssembly Tesseract engine |
| `bmp-js` | Decode BMP images Leptonica can't |
| `idb-keyval` | Browser IndexedDB cache |
| `is-url` | Distinguish URL vs. local path (Node `langPath`) |
| `node-fetch` | `fetch` fallback on older Node |
| `wasm-feature-detect` | Detect SIMD / relaxed-SIMD for core selection |
| `zlibjs` | Browser gunzip of traineddata |
| `opencollective-postinstall` | Funding banner (postinstall) |

### Does NOT exist / NOT supported

- ❌ PDF **input** (output PDF is supported)
- ❌ Handwriting recognition
- ❌ React Native (no WebAssembly)
- ❌ Config files / environment variables (config is programmatic only)
- ❌ Modifying the recognition model or engine accuracy (delegated to upstream Tesseract)
- ❌ Authentication, secrets, telemetry

---

_Generated June 2026 from naptha/tesseract.js at a1ca80d. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
