Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,11 @@ if(LIGHT_OCR_BUILD_FUZZERS)
endfunction()

light_ocr_add_fuzzer(light_ocr_fuzz_image tests/fuzz/image_fuzz.cpp)
light_ocr_add_fuzzer(light_ocr_fuzz_encoded_image
tests/fuzz/encoded_image_fuzz.cpp
bindings/node/src/encoded_image.cpp)
target_link_libraries(light_ocr_fuzz_encoded_image PRIVATE light_ocr::stb)
target_include_directories(light_ocr_fuzz_encoded_image PRIVATE bindings/node/src)
light_ocr_add_fuzzer(light_ocr_fuzz_bundle tests/fuzz/bundle_fuzz.cpp)
light_ocr_add_fuzzer(light_ocr_fuzz_geometry tests/fuzz/geometry_fuzz.cpp)
light_ocr_add_fuzzer(light_ocr_fuzz_lifecycle tests/fuzz/lifecycle_fuzz.cpp
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ It is made for products where OCR should feel like a local capability: quick to
| **On-premise and edge software** | Run a consistent OCR model in kiosks, terminals, appliances, or controlled networks where a cloud dependency is undesirable. |
| **Native and Node.js services** | Embed OCR directly instead of deploying and supervising a separate Python process or OCR daemon. |

The current model is best suited to general text detection and recognition in CJK/Latin mixed content. PDF rendering, encoded-image decoding, document layout analysis, tables, formulas, and translation remain the host application's responsibility.
The current model is best suited to general text detection and recognition in CJK/Latin mixed content. The Node.js adapter can decode in-memory JPEG and PNG inputs; the native core still accepts decoded pixels only. PDF rendering, other image formats, document layout analysis, tables, formulas, and translation remain the host application's responsibility.

## Why this project exists

Cloud OCR is convenient, but it introduces uploads, network availability, recurring cost, and a new privacy boundary. Operating-system OCR APIs avoid the network, but their behavior and availability vary by platform. PaddleOCR offers excellent models, while its usual Python deployment is not always a natural fit for desktop software, native products, or a Node.js application.

`light-ocr` closes that gap with one reusable native core built around official PP-OCRv6 Small models. Applications keep control of image decoding, scheduling, storage, and user experience; the library focuses on turning pixels into structured OCR results.
`light-ocr` closes that gap with one reusable native core built around official PP-OCRv6 Small models. Applications keep control of scheduling, storage, and user experience; the library focuses on turning images into structured OCR results while preserving a raw-pixel native boundary.

## Why use light-ocr

Expand Down Expand Up @@ -105,6 +105,7 @@ The package installs the matching native runtime and the pinned PP-OCRv6 Small m

```ts
import { createEngine } from "@arcships/light-ocr";
import { readFile } from "node:fs/promises";

const engine = await createEngine();
const result = await engine.recognize({
Expand All @@ -114,8 +115,12 @@ const result = await engine.recognize({
stride,
pixelFormat: "rgba8",
});
const encodedResult = await engine.recognizeEncoded(
await readFile("image.jpg"),
);

console.log(result.lines);
console.log(encodedResult.lines);
await engine.close();
```

Expand Down
9 changes: 7 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
| **本地部署与边缘软件** | 在自助终端、设备、边缘节点或受控网络中运行一致的 OCR 模型,摆脱云服务依赖。 |
| **原生与 Node.js 服务** | 把 OCR 直接嵌入应用,不再单独部署和维护 Python 进程或 OCR daemon。 |

当前模型主要面向常规文字检测和 CJK/拉丁字符混排识别。PDF 渲染、编码图片解码、文档版面分析、表格、公式和翻译仍由宿主应用负责。
当前模型主要面向常规文字检测和 CJK/拉丁字符混排识别。Node.js 适配器可以解码内存中的 JPEG/PNG;原生 Core 仍只接受解码后的像素。PDF 渲染、其他图片格式、文档版面分析、表格、公式和翻译仍由宿主应用负责。

## 为什么要做 light-ocr

云 OCR 使用方便,但也带来了图片上传、网络可用性、持续成本和新的隐私边界。操作系统 OCR API 不依赖网络,但各个平台的能力与行为并不一致。PaddleOCR 提供了优秀的模型,不过常见的 Python 部署方式并不总适合桌面软件、原生产品和 Node.js 应用。

`light-ocr` 希望补上这块空白:围绕官方 PP-OCRv6 Small 模型,提供一套可复用的原生核心。应用继续掌控图片解码、任务调度、数据存储和用户体验;light-ocr 专注于把像素稳定地转换为结构化 OCR 结果。
`light-ocr` 希望补上这块空白:围绕官方 PP-OCRv6 Small 模型,提供一套可复用的原生核心。应用继续掌控任务调度、数据存储和用户体验;light-ocr 在保留原生 raw-pixel 边界的同时,把图片稳定地转换为结构化 OCR 结果。

## light-ocr 的优势

Expand Down Expand Up @@ -105,6 +105,7 @@ npm install @arcships/light-ocr

```ts
import { createEngine } from "@arcships/light-ocr";
import { readFile } from "node:fs/promises";

const engine = await createEngine();
const result = await engine.recognize({
Expand All @@ -114,8 +115,12 @@ const result = await engine.recognize({
stride,
pixelFormat: "rgba8",
});
const encodedResult = await engine.recognizeEncoded(
await readFile("image.jpg"),
);

console.log(result.lines);
console.log(encodedResult.lines);
await engine.close();
```

Expand Down
6 changes: 5 additions & 1 deletion bindings/node/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ endif()
add_library(light_ocr_node MODULE
src/addon.cpp
src/bundle_loader.cpp
src/encoded_image.cpp
)

target_include_directories(light_ocr_node PRIVATE
"${LIGHT_OCR_NODE_INCLUDE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/src"
)
target_link_libraries(light_ocr_node PRIVATE light_ocr::core Threads::Threads)
target_link_libraries(light_ocr_node PRIVATE
light_ocr::core
light_ocr::stb
Threads::Threads)
target_compile_definitions(light_ocr_node PRIVATE NAPI_VERSION=8)
set_target_properties(light_ocr_node PROPERTIES
PREFIX ""
Expand Down
10 changes: 8 additions & 2 deletions bindings/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ npm install @arcships/light-ocr
- 原始 Node-API C API,编译为 `NAPI_VERSION=8`,不依赖 `node-addon-api`。
- `createEngine()`、`recognize()`、`close()` 全部返回 Promise。
- 每个 engine 一条专用 C++ worker thread 和一个有界 FIFO;推理不占 JavaScript 线程或 libuv 共享线程池。
- 输入只接受 `Uint8Array` raw pixels:`gray8`、`rgb8`、`bgr8`、`rgba8`。
- `recognize()` 接受 `Uint8Array` raw pixels:`gray8`、`rgb8`、`bgr8`、`rgba8`。
- `recognizeEncoded()` 接受内存中的 JPEG/PNG `Uint8Array`;格式自动检测,解码在 engine worker 中执行。
- `recognize()` 返回前同步复制本次调用实际需要的像素范围;调用返回后可以立即修改或复用原 Buffer。
- 支持 `AbortSignal` 协作式取消:queued 请求会从队列移除;running 请求立即拒绝 public Promise,但 Core 会安全运行到返回并丢弃结果。
- native addon 只接收现有绝对 bundle 目录。当前源码开发调用显式传 `bundlePath`;发布后的 facade 默认使用随 npm 安装的 model package 路径。
- 产品 engine 默认报告 `detectionStrategy: 'bounded'`、`detectionMaxSide: 960` 和 `defaultRecognitionBatchSize: 1`。`detection: {strategy: 'upstreamExact'}` 只用于显式上游对照;单次 `recognize({detectionMaxSide})` 只能继续降低 bounded engine 的 side。

v1 不支持 encoded image、zero-copy/transfer、运行中 inference 硬中断、Electron 或 Bun。详细契约见 [Node-API 设计](../../docs/napi-design.md)。
不支持 WebP、GIF、PDF、EXIF orientation 自动旋转、zero-copy/transfer、运行中 inference 硬中断、Electron 或 Bun。详细契约见 [Node-API 设计](../../docs/napi-design.md)。

## 本地构建

Expand Down Expand Up @@ -96,7 +97,12 @@ async function main() {
signal: controller.signal,
},
);
const encodedResult = await engine.recognizeEncoded(
await require('node:fs/promises').readFile('/absolute/path/to/image.jpg'),
{ signal: controller.signal },
);
console.log(result.lines);
console.log(encodedResult.lines);
} catch (error) {
if (error instanceof OcrError) console.error(error.code, error.message, error.detail);
else throw error; // 包括调用方提供的 AbortSignal.reason
Expand Down
10 changes: 9 additions & 1 deletion bindings/node/js/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ class OcrEngineImpl {
}

recognize(image, options = {}) {
return this.#recognize('recognize', image, options);
}

recognizeEncoded(data, options = {}) {
return this.#recognize('recognizeEncoded', data, options);
}

#recognize(nativeMethod, image, options) {
let signal;
let nativeOptions;
try {
Expand All @@ -136,7 +144,7 @@ class OcrEngineImpl {

let operation;
try {
operation = this.#native.recognize(image, nativeOptions);
operation = this.#native[nativeMethod](image, nativeOptions);
} catch (error) {
return Promise.reject(normalizeNativeError(error));
}
Expand Down
2 changes: 2 additions & 0 deletions bindings/node/js/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export interface Diagnostics {
}
export interface TimingUs {
readonly total: number;
readonly decode: number;
readonly inputValidation: number;
readonly detectionPreprocess: number;
readonly detectionInference: number;
Expand Down Expand Up @@ -170,6 +171,7 @@ export class OcrError extends Error {
export interface OcrEngine {
readonly info: EngineInfo;
recognize(image: RawImage, options?: RecognizeOptions): Promise<OcrResult>;
recognizeEncoded(data: Uint8Array, options?: RecognizeOptions): Promise<OcrResult>;
close(): Promise<void>;
}

Expand Down
Loading
Loading