pt-perkasa-pilar-utama/ppu-paddle-ocr/skill-ppu-paddle-ocr

Claude Code and agent skill by pt-perkasa-pilar-utama/ppu-paddle-ocr

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Use this skill whenever the user is writing TypeScript/JavaScript code that imports `ppu-paddle-ocr`, `ppu-paddle-ocr/web`, or `ppu-paddle-ocr/mobile`, or asks about PaddleOCR / PP-OCRv6 / PP-OCRv5 in JavaScript runtimes - text detection, text recognition, OCR on receipts/invoices/documents, ONNX Runtime OCR, multilingual OCR (Latin, Cyrillic, Arabic, Indic, CJK, Thai), WebGPU OCR in browsers, browser-extension OCR, React Native / Expo OCR on iOS and Android (Skia + onnxruntime-react-native), I…

Install

npx vskill@latest install pt-perkasa-pilar-utama/ppu-paddle-ocr/skill-ppu-paddle-ocr

SKILL.md

Writing code with ppu-paddle-ocr

ppu-paddle-ocr is a TypeScript SDK around the PP-OCR ONNX models (PP-OCRv6 by default; v3-v5 presets available). One package, one API, every JavaScript runtime - Node.js, Bun, Deno, browsers, browser extensions, and React Native (iOS/Android). It pairs a text-detection model with a text-recognition model and decodes them with a character dictionary; the package ships the PP-OCRv6 tiny multilingual models by default (small/medium presets for full-dictionary coverage) and downloads them on first run.

The two things that bite people first are (1) picking the right entry point for the runtime, and (2) understanding that the service has a real lifecycle - you must await initialize() before recognize(), and call destroy() to free ONNX sessions. Everything else is configuration.

The three entry points

Import pathRuntimeCanvas backendProcessing engineModel loading
ppu-paddle-ocrNode.js, Bun, Deno servers and CLIs@napi-rs/canvas via ppu-ocv"opencv" (default, more accurate) or "canvas-native"Cached on disk under ~/.cache/ppu-paddle-ocr
ppu-paddle-ocr/webBrowsers and browser extensionsHTMLCanvasElement / OffscreenCanvasAlways "canvas-native" - OpenCV.js is not bundled in the web entry pointFetched via fetch() every page load (relies on HTTP cache)
ppu-paddle-ocr/mobileReact Native (iOS / Android)@shopify/react-native-skia via ppu-ocv/canvas-mobileAlways "canvas-native" - no OpenCV on RNFetched via fetch() each initialize() (no built-in disk cache)

Hard rules:

  • The peer dependency depends on the entry point: install onnxruntime-node for ppu-paddle-ocr, onnxruntime-web for ppu-paddle-ocr/web, and onnxruntime-react-native + `@shopify/react-native-skia` for ppu-paddle-ocr/mobile. All are optional peer deps so the install only pulls what you actually use.
  • Setting processing.engine = "opencv" on the /web or /mobile import is silently ignored - canvas-native is the only available backend off Node/Bun.
  • PaddleOcrService.downloadModels() and service.clearModelCache() are Node/Bun-only (they touch the filesystem). On the web and mobile builds they are not defined.
  • The /mobile entry needs native modules, so it requires a dev client or `expo prebuild` - not Expo Go. Targets RN >= 0.74 / Expo SDK >= 51 (Hermes). Inference is CPU by default; pass session: { executionProviders: ["nnapi"] } (Android) or ["coreml"] (iOS) to opt into hardware acceleration. There is no WebGPU on RN.

When the user describes their runtime in any concrete way ("Bun CLI", "Cloudflare Workers", "Chrome MV3 service worker", "Vite frontend", "Expo app"), bind the import path to that constraint immediately rather than echoing a generic snippet.

The lifecycle contract

Every program follows the same four beats:

ts
import { PaddleOcrService } from "ppu-paddle-ocr"; // or "ppu-paddle-ocr/web" / "ppu-paddle-ocr/mobile"
const service = new PaddleOcrService(/* PaddleOptions? */);
await service.initialize(); // 1. download/load models, build ONNX sessions
const result = await service.recognize(image); // 2. run detection -> recognition
// ...possibly more recognize() calls reusing the same service...
await service.destroy(); // 3. release ONNX sessions

initialize() is what fetches the models, parses the dictionary, and builds the detection + recognition InferenceSessions. Until it resolves, recognize() will lazy-init on the first call (it's safe but slower and harder to reason about - prefer to await initialize() explicitly at startup).

destroy() calls release() on both ONNX sessions and nulls them out. In a long-lived server, do not destroy after every request - instantiate one PaddleOcrService per process, keep it warm, share it across requests, and only destroy on graceful shutdown. Each initialize() re-downloads the model from disk into the WASM/native ONNX runtime, which is expensive.

isInitialized() is available as a cheap "is this service ready?" check before sending it to a request handler.

What recognize() accepts and returns

recognize(image, options?) accepts:

  • string - must be either an absolute filesystem path (Node/Bun) or an http(s):// URL. Relative paths are rejected with an explicit error. On web and React Native, only URLs work.
  • ArrayBuffer - raw image bytes (PNG/JPEG/WEBP, anything ppu-ocv can decode). The most universal input, and the usual one on React Native (a captured/bundled frame).
  • A canvas - @napi-rs/canvas Canvas on Node/Bun, HTMLCanvasElement / OffscreenCanvas on web, or a Skia-backed canvas on React Native. Useful when you've already preprocessed the image and want to skip a re-decode.

Returns one of:

ts
type PaddleOcrResult = {
text: string; // full text, lines joined by "\n"
lines: RecognitionResult[][]; // grouped by visual line, each line sorted left-to-right
confidence: number; // mean confidence across all items, 0..1
};
type FlattenedPaddleOcrResult = {
text: string; // all items joined by " "
results: RecognitionResult[]; // flat, in reading order
confidence: number;
};
type RecognitionResult = {
text: string;
box: { x: number; y: number; width: number; height: number };
confidence: number;
};

Pass { flatten: true } to get the flat shape - useful for search indexing or when you don't care about line structure. Default is grouped (lines).

If detection finds zero boxes, recognize() returns an empty result (text: "", lines: [] or results: [], confidence: 0) - it does not throw. Callers should branch on result.lines.length === 0 (or result.text === "") rather than wrapping in try/catch.

Detection-only inference: detect()

For a single image, the package entry points also export one-shot ocr() and detect() functions that initialize and destroy a service automatically. Use PaddleOcrService for repeated work so model sessions are reused.

When the user only needs layout - where the text is, not what it says - point them at detect(image, options?) instead of recognize(). It runs just the detection model and returns { boxes: Box[] } in original image coordinates. Same accepted inputs as recognize(), available on all three entry points, and also exposed as the CLI detect command and the serve app's POST /v1/detect.

ts
const { boxes } = await service.detect(imageBuffer);
// PNG crops of each region, index-aligned with boxes
const { crops } = await service.detect(imageBuffer, { crop: true });
// Write crops to disk as crop_000.png, crop_001.png, ... (Node/Bun only)
await service.detect(imageBuffer, { saveCropsTo: "./regions" });

DetectOptions extends DetectionOptions, so any tuning field (maxSideLength, minimumAreaThreshold, paddings, mean, stdDeviation) can be overridden per call without touching the service defaults. Platform limits: saveCropsTo is ignored on web/mobile (no filesystem); crop: true throws on React Native (the Skia canvas has no PNG encoder). detect() results are not cached - the result cache only covers recognize().

The result cache

recognize() caches results in-memory keyed by a hash of the input image bytes. This means calling recognize() twice with the same ArrayBuffer (or the same canvas serialised the same way) returns the cached result instantly - including the same flatten/grouped shape.

Cache is bypassed in two cases:

  • options.noCache === true
  • options.dictionary is set (custom dictionary always re-runs)

Custom dictionaries disable the cache because the cache key only hashes the image, not the decoding alphabet - re-using a cached entry decoded with a different dict would return wrong text.

When benchmarking or iterating on detection/recognition options, set noCache: true or you'll see suspiciously fast "second runs" that are actually just cache hits.

Recognition strategies - the speed/accuracy lever

Three strategies control how detected boxes are batched into recognition inferences:

StrategyInferencesBest for
per-boxOne per box (most)Isolated regions with no line structure; recognizes each region alone.
per-lineOne per visual line (fewer)Default. Line context fixes short-label misreads; fewer inferences.
cross-lineBin-packed batches (fewest)Throughput-sensitive workloads with dense text. Slight accuracy hit possible.

The strategies are within ~10% on speed; per-line is the default because merged line context reads short low-contrast labels more reliably than isolated crops and needs fewer inferences. Switch to cross-line for maximum throughput on dense pages, or per-box when regions are truly independent.

Set globally on construction, or override per call:

ts
// global
const service = new PaddleOcrService({
recognition: { strategy: "cross-line", crossLineWidthFactor: 1.2 },
});
// per call - overrides for this recognize() only
const result = await service.recognize(buffer, { strategy: "per-box" });

crossLineWidthFactor only matters for cross-line - it's a width multiplier on the bin-pack target. Larger packs more lines per batch (faster, slight accuracy loss); smaller keeps lines isolated (slower, closer to per-line accuracy). Default 1.0 is a sane starting point.

When the user says "make it faster" without naming a strategy, suggest cross-line for dense pages; per-line is already the default. When they say "make it more accurate", point them at a larger model (V6_SMALL_MODEL, V6_MEDIUM_MODEL, or a _server_ variant - see below) or at detection tuning.

Processing engines - opencv vs canvas-native

processing.engine controls the image-processing backend used for detection preprocessing and recognition crop resizing.

  • "opencv" (default on Node/Bun): uses ppu-ocv with OpenCV.js. More accurate box geometry, especially around tilted/rotated text. Requires OpenCV.js WASM (loaded via ppu-ocv).
  • "canvas-native": pure canvas operations via ppu-ocv/canvas. Lighter weight, no OpenCV dependency. Slight accuracy loss on the detection side (~1-2% on receipts).
ts
const service = new PaddleOcrService({
processing: { engine: "canvas-native" },
});

The /web entry point always uses canvas-native regardless of this flag - OpenCV.js is not in the web bundle. If a user is on the web build and asks for OpenCV-grade accuracy, the answer is "preprocess upstream with ppu-ocv/web" (which does ship OpenCV), then pass the cleaned canvas into recognize().

Default models, the cache, and how to warm it

By default, initialize() fetches three files from huggingface.co/snowfluke/ppu-paddle-ocr-models and caches them under ~/.cache/ppu-paddle-ocr (Node/Bun only):

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
113
Repository forks
14
Repository npm downloads, 7 days
3,045
7-day trend score
42

Stars and forks are for the whole GitHub repository, not this skill alone. Downloads are for the repository's npm package. Refreshed 9 Jun 2026, 5 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/pt-perkasa-pilar-utama/ppu-paddle-ocr/skill-ppu-paddle-ocr/badge)](https://verified-skill.com/skills/pt-perkasa-pilar-utama/ppu-paddle-ocr/skill-ppu-paddle-ocr)

Something wrong with this skill or its page? Tell us.