light-ocr
July 31, 2026 · View on GitHub
Local OCR for Node.js and Agents. Offline, no Python, no network calls. Returns text with coordinates and confidence.
Installing from npm? Start with the
@arcships/light-ocrpackage guide. This document keeps the lower-level image engine, CLI contract, and source-build details.
Install
npm install @arcships/light-ocr
The install includes the PP-OCRv6 Small model and a prebuilt native runtime for your platform. No post-install scripts, no downloads — createEngine() works out of the box.
CLI
The light-ocr command is available after install.
Quick start
# Recognize text in an image (default action)
light-ocr image.png --format json
# Just text, no coordinates
light-ocr image.png --format text
# PDF pages through the renderer bundled by the main package
light-ocr report.pdf --pages 1-10 --format jsonl
# Engine info
light-ocr info --version
light-ocr info --model-info
# Voluntary system/provider diagnostics
light-ocr doctor --json
Subcommands
| Command | Purpose |
|---|---|
recognize <path> [flags] | Full OCR: detection + recognition (default) |
detect <path> [flags] | Detection only: text region boxes, no recognition |
info --model-info | --version | Engine/version diagnostics, no image read |
doctor [--json] | System/provider diagnostics; never reports a hostname or stable device ID |
light-ocr image.png without a subcommand is implicit recognize.
Common flags
| Flag | Values | Description |
|---|---|---|
--format | json | jsonl | text | Output format (default: json) |
--region | x,y,w,h | Restrict recognition to a pageSpace rectangle |
--provider | auto | cpu | apple | webgpu | Execution provider (default: auto) |
--no-exif | — | Disable EXIF orientation correction |
--schema-version | 1 | Request exact output schema |
--quiet | — | Suppress non-error stderr |
detect does not accept --format (output is always JSON with detections[]). The reserved detect --crop flag currently fails with unsupported_capability; use returned boxes with recognize --region until crop bytes are implemented.
Scenarios
Screenshot with small text:
light-ocr screenshot.png --format json
# Low confidence? Re-run on a specific region:
light-ocr recognize screenshot.png --region 100,80,640,320 --format json
Form/receipt field extraction (detect-then-recognize):
light-ocr detect receipt.png # find where text is
light-ocr recognize receipt.png --region 50,200,300,80 --format json # read that area
stdin / pipe:
cat image.png | light-ocr recognize --stdin --type image/png --format json
Batch via shell:
for f in *.png; do light-ocr recognize "$f" --format jsonl; done
Output schema
{
"schemaVersion": 1,
"source": { "kind": "image", "mediaType": "image/png", "identity": {}, "appliedTransforms": {} },
"pages": [{
"index": 0,
"width": 640,
"height": 480,
"coordinateSpace": "pageSpace",
"structure": "ocr-order",
"lines": [{ "id": "L0", "text": "HELLO", "confidence": 0.99, "box": [{"x":0,"y":0},{"x":100,"y":0},{"x":100,"y":30},{"x":0,"y":30}] }]
}]
}
boxis 4 points inpageSpace(top-left origin, x right, y down, post-EXIF pixels)detectreplaceslineswithdetections[]({ id, score, box }) and setsstructure: "detect"--format text: recognized text only, one line per line--format jsonl: one page record per line (for streaming/batch)
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 64 | Usage error |
| 65 | Invalid argument (bad region, unsupported format/schema) |
| 66 | Invalid image |
| 67 | Unsupported capability |
| 68 | Model/bundle error |
| 69 | Resource limit exceeded |
| 70 | Environment/package failure |
| 71 | Inference failure |
| 72 | Internal error |
Node API
const { createEngine } = require('@arcships/light-ocr');
const engine = await createEngine();
const result = await engine.recognizeEncoded(imageBytes);
console.log(result.lines[0].text); // "HELLO"
console.log(result.lines[0].confidence); // 0.99
console.log(result.lines[0].box); // [{x,y}, {x,y}, {x,y}, {x,y}]
await engine.close();
Detect (detection only, no recognition)
const engine = await createEngine();
const result = await engine.detect(imageBytes);
console.log(result.boxes[0].score); // 0.95
console.log(result.boxes[0].box); // [{x,y}, {x,y}, {x,y}, {x,y}]
await engine.close();
With options
const engine = await createEngine({
execution: { provider: 'auto' },
queueCapacity: 4,
});
const result = await engine.recognizeEncoded(imageBytes, {
applyExif: true, // EXIF orientation correction (default: true)
region: { x: 100, y: 80, width: 640, height: 320 }, // ROI
signal: controller.signal,
});
console.log(engine.info.execution.sessions.detection.actualProviderChain);
await engine.close();
ESM
import { createEngine } from '@arcships/light-ocr';
TypeScript types are included in the shared runtime index.d.ts.
Capabilities
- Formats: JPEG and PNG through the image engine; PDF and multi-page image jobs through the main package's document API
- EXIF orientation: JPEG orientation tags 1–8 automatically corrected
- ROI: pageSpace axis-aligned rectangle, coordinates offset back to full page
- Providers: CPU (all platforms), Apple Core ML (macOS arm64), WebGPU (Linux x64, Windows x64)
- Offline: no network at install or runtime; model bundled in npm package
- Concurrency: one engine = one worker thread, bounded FIFO queue, AbortSignal support
- Platforms: macOS arm64/x64, Linux x64/arm64 (glibc), Windows x64/arm64
- Node.js: 22, 24
Not supported
- WebP, GIF, TIFF
- Character-level coordinates (recognition is line-level)
- Multi-engine fan-out as default architecture
- Bun (Node-API lifecycle not fully verified)
Acceleration
provider: 'auto' (default) selects the best available backend:
| Platform | Auto candidate | Notes |
|---|---|---|
| macOS arm64 | Apple Core ML → CPU | FP16 detection + recognition via Neural Engine / GPU |
| macOS x64 | CPU | Core ML parity not met on Intel |
| Linux x64 | WebGPU → CPU | FP32, bounded CPU partition for Concat/Gather/Slice |
| Windows x64 | WebGPU → CPU | FP32, D3D12 |
| Linux arm64 | CPU | WebGPU plugin has no arm64 binary |
| Windows arm64 | CPU | WebGPU not yet qualified |
Use light-ocr info --model-info to inspect the actual provider chain, device, and qualification status.
Build from source
See CONTRIBUTING.md for prerequisites and the full build guide.
# Clone and initialize
git clone https://github.com/arcships/light-ocr.git
cd light-ocr
# Build C++ core
cmake -S . -B build-ci -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DLIGHT_OCR_DEPENDENCY_CACHE_DIR="$PWD/.cache/dependencies"
cmake --build build-ci --parallel
ctest --test-dir build-ci --output-on-failure
# Build Node addon
cd bindings/node
NODE_INCLUDE_DIR="$(node -p "require('node:path').resolve(require('node:path').dirname(process.execPath), '../include/node')")"
cmake -S ../.. -B build-node -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DLIGHT_OCR_DEPENDENCY_CACHE_DIR="$PWD/../../.cache/dependencies" \
-DLIGHT_OCR_BUILD_NODE=ON -DLIGHT_OCR_BUILD_TESTS=ON -DLIGHT_OCR_BUILD_TOOLS=OFF \
-DLIGHT_OCR_NODE_INCLUDE_DIR="$NODE_INCLUDE_DIR" \
-DLIGHT_OCR_NODE_EXECUTABLE="$(command -v node)"
cmake --build build-node --target light_ocr_node --parallel
Windows builds additionally require LIGHT_OCR_NODE_LIBRARY pointing to node.lib. The build links a delay-load hook so the addon works under Electron as well as Node.
Run tests:
node --test --test-concurrency=1 bindings/node/test/*.test.cjs
License
Apache-2.0. The bundled PP-OCRv6 model inherits PaddleOCR's license terms.