liteparse Documentation

A fast, helpful, and open-source document parser

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy liteparse on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name liteparse -p 8829:8829 --restart always run-llama/liteparse:latest

Technical Documentation: run-llama/liteparse

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

LiteParse


|

|

|

|

|

|
Docs

English | 简体中文

Looking for LiteParse V1? Follow this link to the old code

LiteParse is a standalone OSS PDF parsing tool focused exclusively on fast and light parsing. It provides high-quality spatial text parsing with bounding boxes, without proprietary LLM features or cloud dependencies. Everything runs locally on your machine.

Hitting the limits of local parsing?
For complex documents (dense tables, multi-column layouts, charts, handwritten text, or
scanned PDFs), you'll get significantly better results with LlamaParse,
our cloud-based document parser built for production document pipelines. LlamaParse handles the
hard stuff so your models see clean, structured data and markdown.

Sign up for LlamaParse free

Overview

  • Fast Text Parsing: Spatial text parsing using PDFium, ~2-5ms per page
  • Flexible OCR System:
    • Built-in: Tesseract (zero setup, bundled with the library)
    • HTTP Servers: Plug in any OCR server (EasyOCR, PaddleOCR, custom)
    • Standard API: Simple, well-defined OCR API specification
  • Complexity Detection: Cheaply check whether a document needs OCR or heavier parsing — route, reject, or estimate cost before a full parse
  • Screenshot Generation: Generate high-quality page screenshots for LLM agents
  • Multiple Output Formats: Markdown, JSON, and Text
  • Markdown Output: Structured Markdown with headings, tables, lists, images, and links — great for feeding LLMs and RAG pipelines
  • Bounding Boxes: Precise text positioning information
  • Multi-language: Use from Rust, Node.js/TypeScript, Python, or the browser (WASM)
  • Worker Pool Mode (Python & Node.js): Parse in persistent worker processes for true parallelism (PDFium otherwise serializes concurrent parses) and hard per-parse timeouts — rogue documents are killed, identified by name, and never stall the pipeline
  • Multi-platform: Linux, macOS (Intel/ARM), Windows
text
/* Detailed source-code truncated for AI context efficiency. */

Benchmarks

LiteParse is measured on multiple public Doc→Markdown benchmarks. All numbers below were produced on
this machine from one command (see Reproducing), with every tool at
its latest release as of 2026-09-09. LiteParse and the "model-free" competitors use no ML model at
all
(no LLM, no layout model, no GPU). All tested methods have permissive licenses and can be
run locally with minimal dependencies.

Benchmark Metric LiteParse + Tesseract OCR + PaddleOCR Best other model-free tool
ParseBench (2,049 docs) Overall (mean of 5 categories) 0.364 0.380 0.389 pdf-inspector 0.283
opendataloader-bench (200 docs) Overall (NID + TEDS + MHS) 0.886 0.896 0.901 opendataloader 0.842
olmOCR-bench (1,403 pages) % tests passed 39.6 41.1 42.2 pdf-inspector 33.7

ParseBench

Rule-based scoring, no LLM judge. Each column is ParseBench's canonical per-category metric
(Tables = GriTS/TRM composite; the others are rule pass-rates). Overall is the mean of the five,
as on the ParseBench leaderboard.

Pipeline Overall Tables Charts Content Faithfulness Semantic Formatting Visual Grounding
LiteParse + PaddleOCR 0.389 0.430 0.013 0.787 0.402 0.314
LiteParse + Tesseract 0.380 0.428 0.012 0.751 0.399 0.307
LiteParse (no OCR) 0.364 0.424 0.013 0.700 0.385 0.297
pdf-inspector 1.19 0.283 0.277 0.017 0.598 0.426 0.099
opendataloader 2.5.7 0.277 0.349 0.006 0.663 0.258 0.110
markitdown 0.1.7 0.185 0.158 0.020 0.652 0.001 0.110

Notes:

  • Visual Grounding scores layout blocks with bounding boxes (lit parse --extract-blocks).
  • Charts is near zero for every tool here — none reconstruct chart data.

opendataloader-bench

NID = reading-order similarity, TEDS = table structure, MHS = heading hierarchy. Overall is the
harness's own mean.

Engine Overall NID TEDS MHS
LiteParse + PaddleOCR 0.901 0.932 0.832 0.840
LiteParse + Tesseract 0.896 0.928 0.829 0.828
LiteParse (no OCR) 0.886 0.917 0.818 0.821
nutrient (commercial) 0.885 0.925 0.708 0.819
opendataloader 2.5.7 0.842 0.912 0.483 0.757
markitdown 0.1.7 0.589 0.844 0.273 0.000

Notes:

  • nutrient has no runnable parser in this harness (commercial)
  • The corpus is native-text PDFs, so OCR gains come from embedded figures and a handful of
    scanned pages.

olmOCR-bench

Score = average of per-category pass rates. The two math categories require LaTeX output and
are 0% for every tool here; they still count in the average.

Engine Overall baseline headers_footers multi_column table_tests long_tiny_text old_scans arxiv_math old_scans_math
LiteParse + PaddleOCR 42.2 99.9 48.7 69.1 54.0 46.4 19.4 0.0 0.0
LiteParse + Tesseract 41.1 99.9 52.1 66.2 54.1 42.5 13.9 0.0 0.0
LiteParse (no OCR) 39.6 99.9 55.8 66.3 52.5 29.2 13.3 0.0 0.0
pdf-inspector 1.19 33.7 82.9 62.1 49.7 43.6 17.6 13.3 0.0 0.0
opendataloader 2.5.7 32.5 86.9 36.6 63.7 24.9 34.8 13.3 0.0 0.0
markitdown 0.1.7 28.7 86.8 38.8 39.3 19.9 31.2 13.3 0.0 0.0

Notes:

  • headers_footers expects letterhead and footer text to be absent. OCR recovers that text
    from logo and address images on single-page documents, where the repeated-header filter cannot
    fire, so the OCR rows score lower there by design. We chose not to drop text by page position.
    pdf-inspector 1.19 refuses scanned and image-based pages outright (192 of the 1,403), which is
    why it tops this category while scoring nothing on those pages elsewhere.
  • old_scans is largely cursive handwriting; Tesseract cannot read it, PaddleOCR partially can.

How the runs are configured

  • Same scorer for every tool. Each benchmark's own evaluator, unmodified. The ground truth in
    all three corpora is plain text, so every tool including LiteParse runs with hyperlink syntax
    off (--no-links); everything else is the default lit parse --format markdown.
  • OCR modes. No OCR is --no-ocr. Tesseract is the built-in engine with no setup.
    PaddleOCR is the PP-OCRv5 mobile detector and English recognizer served over the
    OCR HTTP API by ocr/rapidocr, which runs the PaddleOCR
    models on ONNX Runtime — the same models as ocr/paddleocr, just 20–40× faster
    per page on CPU-only machines.
  • Competitors are the free converters we could run locally, each at its latest release on
    2026-09-09: markitdown 0.1.7, opendataloader-pdf 2.5.7 (its non-hybrid mode),
    pdf-inspector 1.19.0. Each benchmark's own runner for the tool is used as-is. Numbers from public
    leaderboards or LLM-assisted modes are not mixed in.
  • Machine: Apple M2 Max, 12 cores, 32 GB. LiteParse rows: v2.14.4 (2026-09-09).

Reproducing the benchmarks

sh
./run_benchmarks.sh --liteparse-only                # all three benches, no OCR
./run_benchmarks.sh --liteparse-only --ocr=tesseract
( cd ocr/rapidocr && uv run server.py ) &           # then:
./run_benchmarks.sh --liteparse-only --ocr=paddle
./run_benchmarks.sh --competitors-only              # re-run the free competitors only
./run_benchmarks.sh                                 # everything

Results and a SUMMARY.md land in bench_results/latest/.

Installation

Install via your preferred package manager. All versions (except WASM) ship with the same lit CLI.

Language Install Library Docs
Node.js / TypeScript npm i @llamaindex/liteparse Node.js README
Python pip install liteparse Python README
Rust cargo install liteparse (CLI) / cargo add liteparse (lib) Rust README (crates.io)
Browser (WASM) npm i @llamaindex/liteparse-wasm WASM README

Agent Skill

You can use liteparse as an agent skill, downloading it with the skills CLI tool:

bash
npx skills add run-llama/llamaparse-agent-skills --skill liteparse

Or copy-pasting the SKILL.md file to your own skills setup.

See the Agent Skill guide for requirements and usage patterns.

CLI Usage

The CLI is the same across all installations (npm, pip, cargo install).

Parse Files

bash
# Basic parsing
lit parse document.pdf

# Parse to Markdown — headings, tables, lists, images, links
lit parse document.pdf --format markdown -o output.md

# Parse with specific format
lit parse document.pdf --format json -o output.json

# Parse specific pages
lit parse document.pdf --target-pages "1-5,10,15-20"

# Parse without OCR
lit parse document.pdf --no-ocr

# Include page-scoped vector path data in JSON
lit parse document.pdf --format json --extract-vector-graphics

# Include rich per-item PDF text metadata
lit parse document.pdf --format json --extract-text-metadata

# Include page annotations in structured JSON
lit parse document.pdf --format json --extract-annotations

# Include AcroForm widget fields and values (repairs orphaned widgets in memory)
lit parse document.pdf --format json --extract-form-fields

# Parse a remote PDF
curl -sL https://example.com/report.pdf | lit parse -

Markdown Output

LiteParse can render documents directly to Markdown. This means reconstructing headings,
tables, lists, images, and links from the spatial layout. This is ideal for
feeding documents to LLMs and RAG pipelines. This mode is purely heuristics and rule-based,
so complex documents may not render perfectly, but it will be fast.

bash
# Render to Markdown
lit parse document.pdf --format markdown -o output.md

# Strip images instead of emitting placeholders
lit parse document.pdf --format markdown --image-mode off

# Extract embedded images to disk and reference them from the markdown
lit parse document.pdf --format markdown --image-mode embed --extract-images --image-output-dir ./images

# Extract image bytes and metadata without changing Markdown image handling
lit parse document.pdf --format json --extract-images

# Emit link text as plain text (no [text](url) syntax)
lit parse document.pdf --format markdown --no-links

# Include tagged-PDF logical structure in JSON
lit parse document.pdf --format json --extract-structure-tree

# Include the classified layout blocks (with bounding boxes) in JSON
lit parse document.pdf --format json --extract-blocks

Image handling is controlled by --image-mode:

Mode Behavior
placeholder (default) Emits `` references in reading order
off Strips images entirely
embed Emits the same image references as placeholder

--extract-images is the only option that enables embedded-image extraction.
--image-output-dir requires it and writes the extracted bytes to disk. JSON output
contains each image's name, path, page bbox, intrinsic pixel dimensions, rotation,
format, and duplicate relationship; pixel bytes are never embedded in JSON. Identical
image resources reuse the same output file.

Library callers can opt in with extract_images: true (Rust), extractImages: true
(Node/WASM), or extract_images=True (Python). It defaults to false. Markdown image
mode controls presentation only; placeholder refs are still discovered without bytes.

Markdown reconstruction quality varies with document complexity. For the
hardest documents (dense tables, multi-column layouts, scans),
LlamaParse
remains the most accurate option.

Vector Graphics

Vector path output is opt-in because path-heavy PDFs can produce large payloads.
Enable it with --extract-vector-graphics, Rust/Python
extract_vector_graphics = true, or JavaScript/WASM
extractVectorGraphics: true. Each page then includes vector_graphics
(vectorGraphics in JavaScript) with:

  • shapes: path bounding box, stroke/fill paint state and ARGB colors, and
    whether the path contains a Bezier curve.
  • lines: compatible horizontal/vertical segments merged using stroke width
    and paint colors, with top-left 72-DPI viewport coordinates.

The representation follows LlamaParse PDFium path extraction; LiteParse calls
the shape rectangle bbox rather than PDFium's coords, and uses width /
height rather than w / h. The field is absent (or None/undefined) by
default. Diagonal and curved segments are represented by their parent shape but
are not emitted as lines.

Tagged PDF structure tree

Enable --extract-structure-tree (Rust/Python extract_structure_tree,
JavaScript/WASM extractStructureTree) to add a page-scoped structure_tree.
It preserves every root and recursively exposes element type, ID, actual/alternate
text, title, typed scalar attributes, marked-content IDs, children, and referenced
link annotations. The field is absent by default; enabled untagged pages contain
roots: [].

Layout blocks

The Markdown renderer works by classifying each page into blocks — headings,
paragraphs, list items, tables, code, rules, figures — and then rendering them.
Enable --extract-blocks (Rust/Python extract_blocks, JavaScript/WASM
extractBlocks) to get that decomposition as data instead of only as rendered
text, with the coordinates the classifier used.

Each page gains a blocks array in reading order — the same order, and the same
blocks, the Markdown output is built from. Every block carries:

  • kind: one of heading, paragraph, list_item, code, table,
    grid_fallback, rule, figure.
  • bbox: the region it occupies, in the same top-left 72-DPI viewport space as
    text_items. This is the union of every source line that fed the block, so a
    wrapped heading or a multi-line paragraph reports its whole band.
  • Kind-specific fields, omitted when they don't apply: text and level for
    headings; ordered / marker for list items; lines and lang for code;
    header and rows for tables; id / format for figures.

Table cells are objects, not bare strings — each has text and its own bbox,
so a cell can be mapped back to the region of the page it was read from. For
ruled tables that box is the drawn grid cell; for borderless tables it is the
extent of the spans the cell was built from. Cells that exist only to square off
a ragged grid carry no bbox, since they have no ink behind them.

json
{
  "kind": "table",
  "bbox": { "x": 72.0, "y": 310.5, "width": 468.0, "height": 96.0 },
  "header": [
    { "text": "Territory Code", "bbox": { "x": 72.0, "y": 310.5, "width": 120.0, "height": 24.0 } },
    { "text": "Factor",         "bbox": { "x": 192.0, "y": 310.5, "width": 96.0, "height": 24.0 } }
  ],
  "rows": [
    [
      { "text": "001", "bbox": { "x": 72.0, "y": 334.5, "width": 120.0, "height": 24.0 } },
      { "text": "1.25", "bbox": { "x": 192.0, "y": 334.5, "width": 96.0, "height": 24.0 } }
    ]
  ]
}

Document metadata, content bounds, and XFA packets

Parse results (Rust/Node/Python APIs) carry the document's /Info creator
and producer entries when present; these are API-only and never appear in
CLI JSON output. Enable extract_document_metadata (JavaScript/WASM
extractDocumentMetadata) to add doc_meta/docMeta, a provenance object
with the /Info creation/modification dates, PDF version and encryption
permissions, signature state, incremental-save markers, trailer ID comparison,
the document catalog's XMP packet (capped at 64 KiB, with xmp_truncated
when it was cut), and source file size. It is off by default because it
streams the whole source file once; it is absent for inputs converted from a
non-PDF format, where the facts would describe the intermediate PDF rather
than your file. xmp needs a structural parse of the document, so it is
skipped (left absent) for sources over 16 MiB and in WASM builds — the other
fields are unaffected.
Enable --extract-content-bounds (Rust/Python
extract_content_bounds, JavaScript/WASM extractContentBounds) to add a
per-page content_bounds: the union bbox of the page's top-level content
objects in viewport coords (absent for empty pages). Enable
--extract-xfa-packets (Rust/Python extract_xfa_packets, JavaScript/WASM
extractXfaPackets) to add xfa_packets with each raw XFA packet's index,
name, byte length, and XML content; non-XFA documents yield an empty list.
All of these are off by default, so default JSON output is unchanged.

Screenshot raster signals

Screenshots draw AcroForm field appearances (filled values, checkbox states)
on top of the page raster, so form data is visible in the render and to OCR.
Each screenshot result reports is_solid_fill (blank page after render), and
with detect_screenshot_rects (Node detectScreenshotRects) also rects:
solid same-color rectangles and lines found in the raster in viewport coords,
which covers scanned/flattened pages that carry no vector paths.

Check Complexity

Before committing to a full parse, check whether a document actually needs OCR or
heavier processing. This is a cheap, text-layer-only pass — useful for routing
documents to different pipelines, rejecting ones you can't handle, or estimating cost.

bash
# Print the complexity verdict and per-page JSON
lit is-complex document.pdf

# Use as a shell predicate — only parse with --no-ocr when the document is simple
lit is-complex document.pdf --quiet && lit parse document.pdf --no-ocr

# List the pages that need OCR
lit is-complex document.pdf --compact | jq '[.[] | select(.needs_ocr) | .page_number]'

It always prints per-page JSON to stdout, a human-readable verdict to stderr, and
exits non-zero when any page needs OCR. Each page carries a needs_ocr verdict and a
list of reasons (scanned, no-text, sparse-text, embedded-images, garbled,
vector-text, annotation-text).

Batch Parsing

Parse an entire directory of documents:

bash
lit batch-parse ./input-directory ./output-directory

Generate Screenshots

Screenshots are essential for LLM agents to extract visual information that text alone cannot capture.

bash
# Screenshot all pages
lit screenshot document.pdf -o ./screenshots

# Screenshot specific pages
lit screenshot document.pdf --target-pages "1,3,5" -o ./screenshots

# Custom DPI
lit screenshot document.pdf --dpi 300 -o ./screenshots

CLI Reference

Parse Command

text
/* Detailed source-code truncated for AI context efficiency. */

Batch Parse Command

text
lit batch-parse [OPTIONS] <input-dir> <output-dir>

Options:
      --format <format>        Output format: json|text|markdown [default: text]
      --no-ocr                 Disable OCR
      --ocr-language <lang>    OCR language [default: eng]
      --ocr-server-url <url>   HTTP OCR server URL
      --tessdata-path <path>   Path to tessdata directory
      --max-pages <n>          Max pages per file [default: 1000]
      --dpi <dpi>              Rendering DPI [default: 150]
      --recursive              Recursively search input directory
      --extension <ext>        Only process files with this extension (e.g., ".pdf")
      --password <password>    Password for encrypted documents
      --num-workers <n>        Concurrent OCR workers
  -q, --quiet                  Suppress progress output
  -h, --help                   Print help

Screenshot Command

text
lit screenshot [OPTIONS] <file>

Options:
  -o, --output-dir <dir>       Output directory [default: ./screenshots]
      --target-pages <pages>   Pages to screenshot (e.g., "1,3,5" or "1-5")
      --dpi <dpi>              Rendering DPI [default: 150]
      --password <password>    Password for encrypted documents
  -q, --quiet                  Suppress progress output
  -h, --help                   Print help

Is-Complex Command

text
lit is-complex [OPTIONS] <file>

Options:
      --compact                Emit dense, whitespace-free JSON instead of pretty-printed
      --max-pages <n>          Max pages to check [default: 1000]
      --target-pages <pages>   Pages to check (e.g., "1-5,10,15-20")
      --password <password>    Password for encrypted documents
  -q, --quiet                  Suppress the stderr verdict
  -h, --help                   Print help

Prints per-page JSON to stdout and a COMPLEX/SIMPLE verdict to stderr; exits non-zero
when any page needs OCR, so it composes as a shell predicate.

OCR Setup

Default: Tesseract

Tesseract is bundled and works out of the box:

bash
lit parse document.pdf                    # OCR enabled by default
lit parse document.pdf --ocr-language fra # Specify language
lit parse document.pdf --no-ocr           # Disable OCR

For offline or air-gapped environments, set TESSDATA_PREFIX to a directory containing pre-downloaded .traineddata files:

bash
export TESSDATA_PREFIX=/path/to/tessdata
lit parse document.pdf --ocr-language eng

Or pass the path directly:

bash
lit parse document.pdf --tessdata-path /path/to/tessdata

Optional: HTTP OCR Servers

For higher accuracy or better performance, you can use an HTTP OCR server. We provide ready-to-use example wrappers for popular OCR engines:

You can integrate any OCR service by implementing the simple LiteParse OCR API specification (see OCR_API_SPEC.md).

The API requires:

  • POST /ocr endpoint
  • Accepts file and language parameters
  • Returns JSON: { results: [{ text, bbox: [x1,y1,x2,y2], confidence }] }

Multi-Format Input Support

LiteParse supports automatic conversion of various document formats to PDF before parsing.

Supported Input Formats

Office Documents (via LibreOffice)

  • Word: .doc, .docx, .docm, .odt, .rtf, .pages
  • PowerPoint: .ppt, .pptx, .pptm, .odp, .key
  • Spreadsheets: .xls, .xlsx, .xlsm, .ods, .csv, .tsv, .numbers

Install LibreOffice for automatic conversion:

bash
# macOS
brew install --cask libreoffice

# Ubuntu/Debian
apt-get install libreoffice

# Windows
choco install libreoffice-fresh

On Windows, you may need to add LibreOffice's program directory (usually C:\Program Files\LibreOffice\program) to your PATH.

Images (native support)

  • Formats: .jpg, .jpeg, .png, .gif, .bmp, .tiff, .webp, .svg

![NOTE]

As of v2.8.0, imagemagick is no longer required to convert images to PDF. Conversion is natively
handled by the rust code.

Environment Variables

Variable Description
TESSDATA_PREFIX Path to a directory containing Tesseract .traineddata files. Used for offline/air-gapped environments.

Development

The project is a Rust workspace with the core library and language-specific binding crates.

text
crates/
├── liteparse/          # Core library + CLI binary
├── liteparse-napi/     # Node.js bindings (napi-rs)
├── liteparse-python/   # Python bindings (PyO3)
├── liteparse-wasm/     # WASM bindings (wasm-bindgen)
├── pdfium/             # PDFium Rust wrapper
└── pdfium-sys/         # PDFium FFI bindings
packages/
├── node/               # npm package (TS wrapper + native binary)
├── python/             # PyPI package (Python wrapper + native binary)
└── wasm/               # WASM npm package

Building

bash
# Build the CLI
cargo build --release -p liteparse

# Build Node.js bindings
cd packages/node && npm run build

# Build Python bindings
cd packages/python && maturin develop --release

# Build WASM
cd packages/wasm && npm run build

We provide a fairly rich AGENTS.md/CLAUDE.md that we recommend using to help with development + coding agents.

License

Apache 2.0

Credits

Built on top of:


Overview (`dataset_eval_utils/README.md`)

LiteParse Eval Utils

Utilities for generating and evaluating datasets for PDF parsing performance. Compares text extraction quality across multiple PDF parsers using LLM-based QA evaluation.

Setup

Requires Python 3.12+.

bash
# Install the package (from the dataset_eval_utils directory)
pip install -e .

You'll need an ANTHROPIC_API_KEY environment variable set for the LLM-based evaluation and dataset processing tools.

Dataset

An existing dataset that was generated and evaluated using this framework can be found on huggingface.

You can download the dataset using the Hugging Face CLI:

bash
hf download run-llama/liteparse-eval-dataset --repo-type dataset --local-dir ./liteparse-eval-dataset

CLI Tools

`lp-process` — Generate Ground Truth Datasets

Processes PDF and image files using Claude's vision capabilities to generate structured QA ground truth data.

bash
lp-process /path/to/documents --output-dir ./ground_truth

Options:

  • --output-dir — Directory to save output JSON files (default: ./output)
  • --model — Claude model to use (default: claude-sonnet-4-5-20250929)
  • --api-key — Anthropic API key (or set ANTHROPIC_API_KEY env var)

Each output JSON file contains document metadata and QA pairs extracted from the document pages.

`lp-evaluate` — Run QA Evaluation

Evaluates parser text extraction quality by having an LLM answer questions from extracted text and judging correctness against ground truth answers.

bash
lp-evaluate \
  --data-dir ./documents \
  --ground-truth-dir ./ground_truth \
  --parse-provider liteparse \
  --output ./results/run1

Options:

  • --data-dir — Directory containing source PDF documents (required)
  • --ground-truth-dir — Directory containing ground truth JSON files (required)
  • --output — Path to save results (JSON + HTML report)
  • --parse-provider — Parser to evaluate: liteparse, pymupdf, pypdf, markitdown (default: liteparse)
  • --llm-provider — LLM for answering questions: anthropic (default: anthropic)

Outputs:

  • <output>.json — Aggregate results with pass rates
  • <output>_detailed.json — Per-document results with extracted text and individual QA results
  • <output>_report.html — Interactive HTML report with PDF previews and QA breakdowns

`lp-benchmark` — Performance Benchmarking

Measures parse latency and memory usage across providers.

bash
lp-benchmark ./documents --providers pymupdf liteparse --warmup-runs 0

Takes a directory of PDFs (searched recursively), not a single file.

Options:

  • --providers — Providers to benchmark (default: all local providers)
  • --warmup-runs — Full passes over the corpus before timing starts (default: 5).
    Each warmup run parses every document, so on a large corpus this dominates
    wall time; use 0 for a quick measurement.
  • --output — Path to save JSON results

Reports per-document latency plus TOTAL / AVG-per-doc / MS-per-page rows.

Parser Providers

Provider Library Notes
liteparse liteparse Spatial text extraction with OCR support
pymupdf PyMuPDF Fast, mature PDF library
pypdf pypdf Pure-Python PDF library
markitdown MarkItDown Microsoft's document-to-markdown converter

Evaluation Pipeline

  1. Extract text from PDF using the selected parser provider
  2. Answer questions — LLM reads the extracted text and answers ground truth questions
  3. Judge answers — A separate LLM judge evaluates whether predicted answers are semantically equivalent to expected answers
  4. Aggregate — Pass rates are computed per-document and overall

Overview (`ocr/README.md`)

ocr/

Example OCR server implementations that conform to the LiteParse OCR API specification.

These servers allow you to use alternative OCR engines instead of the built-in Tesseract.js.

Why Use an External OCR Server?

Feature Tesseract.js (built-in) EasyOCR PaddleOCR Surya
Setup Zero (included) uv uv uv
Speed Moderate Moderate Fast (2-3x) Moderate (GPU recommended)
Accuracy (Latin) Good Good Good Excellent
Accuracy (CJK) Fair Good Excellent Excellent
Languages 100+ 80+ 80+ 91
Memory In-process Separate Separate Separate

Recommendations:

  • Quick start: Use built-in Tesseract (no setup)
  • Asian languages: Use PaddleOCR (best CJK support)
  • Multilingual / broad scripts: Use Surya (strong multilingual accuracy)
  • General use: EasyOCR (good balance)

Available Servers

[easyocr/](./easyocr/)

Flask server wrapping EasyOCR library.

  • Port: 8828
  • Good general-purpose OCR
  • 80+ languages

[paddleocr/](./paddleocr/)

Flask server wrapping PaddleOCR library.

  • Port: 8829
  • Excellent for Chinese, Japanese, Korean
  • 2-3x faster than EasyOCR

[suryaocr/](./suryaocr/)

FastAPI server wrapping Surya OCR 2.

  • Port: 8830
  • Multilingual foundation model (90+ languages)
  • Block-level output; GPU recommended

Quick Start

bash
# Start EasyOCR server
cd ocr/easyocr
uv run server.py

# OR start PaddleOCR server
cd ocr/paddleocr
uv run server.py

# OR start Surya OCR server
cd ocr/suryaocr
uv run server.py

Then use with LiteParse:

bash
# CLI
lit parse document.pdf --ocr-server-url http://localhost:8828/ocr

# Code
const parser = new LiteParse({
  ocrServerUrl: 'http://localhost:8828/ocr',
  ocrLanguage: 'en',
});

API Specification

All servers implement the same API (defined in OCR_API_SPEC.md):

Endpoint: POST /ocr

Request:

  • Content-Type: multipart/form-data
  • Fields:
    • file - Image file
    • language - Language code (e.g., 'en', 'zh', 'ja')

Response:

json
{
  "results": [
    {
      "text": "recognized text",
      "bbox": [x1, y1, x2, y2],
      "confidence": 0.95
    }
  ]
}

Creating a Custom OCR Server

To implement your own OCR server:

  1. Create a Flask/FastAPI/Express server

  2. Accept POST /ocr with multipart form data

  3. Return JSON with results array containing:

    • text - Recognized text string
    • bbox - Bounding box as [x1, y1, x2, y2]
    • confidence - Confidence score (0-1)
  4. (Optional) Implement GET /health endpoint

See the existing servers as reference implementations.

Language Codes

Most servers accept ISO 639-1 codes (e.g., 'en', 'zh', 'ja') and map them internally:

ISO Code Language Notes
en English
zh Chinese (Simplified)
zh-tw Chinese (Traditional)
ja Japanese
ko Korean
fr French
de German
es Spanish
ar Arabic
hi Hindi

Overview (`crates/liteparse/README.md`)

LiteParse


Rust library and CLI for fast, lightweight PDF and document parsing with spatial text extraction. Runs entirely locally with zero cloud dependencies.

LiteParse is also available for Node.js/TypeScript, Python, and the browser (WASM). See the project README for all options.

Installation

Add to your Cargo.toml:

toml
[dependencies]
liteparse = "2"

Or install the CLI:

bash
cargo install liteparse

Quick Start

rust
use liteparse::{LiteParse, LiteParseConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let parser = LiteParse::new(LiteParseConfig::default());
    let result = parser.parse("document.pdf").await?;

    println!("{}", result.text);

    for page in &result.pages {
        println!("Page {}: {} text items", page.page_num, page.text_items.len());
    }

    Ok(())
}

Configuration

rust
use liteparse::{LiteParse, LiteParseConfig, OutputFormat};

let config = LiteParseConfig {
    ocr_enabled: true,                    // Enable OCR (default: true)
    ocr_language: "eng".to_string(),      // Tesseract language code
    ocr_server_url: None,                 // HTTP OCR server URL (optional)
    tessdata_path: None,                  // Path to tessdata directory (optional)
    max_pages: 1000,                      // Max pages to parse
    target_pages: Some("1-5,10".into()),  // Specific pages (optional)
    dpi: 150.0,                           // Rendering DPI
    output_format: OutputFormat::Json,    // Json | Text | Markdown
    extract_annotations: false,           // Include page annotations in output
    extract_structure_tree: false,        // Include tagged-PDF logical structure
    preserve_very_small_text: false,      // Keep tiny text
    extract_text_metadata: false,         // Opt in to rich PDF text metadata
    password: None,                       // Password for protected documents
    quiet: false,                         // Suppress progress output
    ..Default::default()
};

let parser = LiteParse::new(config);

Set extract_annotations: true to populate ParsedPage::annotations with
annotation subtype, contents, author/title, PDF date strings, viewport-space
rectangle and quadpoint rectangles, and external link URI. It is independent
of extract_links, which controls Markdown link rendering. The field is
None when extraction is disabled.

Set extract_structure_tree: true to populate ParsedPage::structure_tree with
the complete tagged-PDF hierarchy: all roots, element type/ID, actual and alternate
text, title, typed scalar attributes, MCIDs, recursive children, and referenced link
annotations. Disabled pages use None; enabled untagged pages have no roots.

Markdown Output

LiteParse can render documents directly to Markdown, including headings, tables, lists,
images, and links reconstructed from the spatial layout. Set
output_format: OutputFormat::Markdown; the rendered Markdown is returned on
result.text. Two related knobs control Markdown rendering:

  • image_mode (ImageMode::Placeholder default | Off | Embed) — how raster
    images are surfaced in the output.
  • extract_images (default false) — return embedded image bytes and metadata
    without changing Markdown image handling. This is the only option that enables
    extraction.
  • image_output_dir — write extracted image files and return their names/paths;
    requires extract_images: true. Duplicate image resources reuse the same file.
  • extract_links (default true) — render hyperlink annotations as
    [text](url); set false for plain anchor text.
rust
use liteparse::config::{ImageMode, LiteParseConfig, OutputFormat};

let config = LiteParseConfig {
    output_format: OutputFormat::Markdown,
    image_mode: ImageMode::Placeholder,
    extract_images: true,
    image_output_dir: Some("./images".into()),
    extract_links: true,
    ..Default::default()
};
let result = LiteParse::new(config).parse("document.pdf").await?;
println!("{}", result.text); // rendered Markdown

Reconstruction quality varies with document complexity.

Parsing from Bytes

rust
use liteparse::types::PdfInput;

let pdf_bytes: Vec<u8> = std::fs::read("document.pdf")?;
let result = parser.parse_input(PdfInput::Bytes(pdf_bytes)).await?;
println!("{}", result.text);

Document Complexity

Before committing to a full parse, check whether a document needs OCR or heavier
processing. is_complex is a cheap, text-layer-only pass that returns a
PageComplexityStats per page with a needs_ocr verdict and the signals behind it —
useful for routing documents to different pipelines, rejecting ones you can't handle, or
estimating cost.

rust
use liteparse::types::PdfInput;

let parser = LiteParse::new(LiteParseConfig::default());
let pages = parser.is_complex(PdfInput::Path("document.pdf".into())).await?;

if pages.iter().any(|p| p.needs_ocr) {
    // Route to the OCR-enabled pipeline, inspect `p.reasons`, etc.
    for page in pages.iter().filter(|p| p.needs_ocr) {
        println!("Page {} needs OCR: {:?}", page.page_number, page.reasons);
    }
}

reasons is a Vec<ComplexityReason> (Scanned, NoText, SparseText,
EmbeddedImages, Garbled, VectorText, AnnotationText); new variants may be added over time, so match
leniently.

Custom OCR Engine

Implement the OcrEngine trait to plug in your own OCR backend:

rust
use liteparse::ocr::OcrEngine;
use std::sync::Arc;

let parser = LiteParse::new(LiteParseConfig::default())
    .with_ocr_engine(Arc::new(my_engine));

For a native ONNX backend, enable oar-ocr and supply a detection model,
recognition model, and matching character dictionary:

rust,no_run
use liteparse::ocr::oar::OarOcrEngine;
use liteparse::{LiteParse, LiteParseConfig};
use std::path::Path;
use std::sync::Arc;

let models = Path::new("models");
let engine = OarOcrEngine::from_models(
    models.join("pp-ocrv6_small_det.onnx"),
    models.join("pp-ocrv6_small_rec.onnx"),
    models.join("ppocrv6_dict.txt"),
)?;

let parser = LiteParse::new(LiteParseConfig::default())
    .with_ocr_engine(Arc::new(engine));
# Ok::<(), Box<dyn std::error::Error>>(())

For opt-in model downloads, enable oar-ocr-auto-download and use a preset. On
first use, oar-ocr downloads the detection model, recognition model, and
matching dictionary from ModelScope, verifies their SHA-256 digests, and caches
them under $OAR_HOME (default ~/.oar):

rust,no_run
use liteparse::ocr::oar::OarOcrEngine;

// Smallest / fastest PP-OCRv6 configuration.
let engine = OarOcrEngine::ppocr_v6_tiny()?;
# Ok::<(), Box<dyn std::error::Error>>(())

Presets cover the current and previous PP-OCR generations, from fastest to most
accurate: ppocr_v6_tiny, ppocr_v6_small, and ppocr_v6_medium. Each wires the correct
detector/recognizer/dictionary trio. For PP-OCRv4, a language-specific
recognizer, or a custom mix, use from_models with a matching dictionary.

To mix a detector, recognizer, and dictionary yourself, pass registered bare
file names to from_models. Pair the recognizer with its matching dictionary —
the tiny recognizer needs ppocrv6_tiny_dict.txt, while the larger models use
ppocrv6_dict.txt; a mismatched dictionary silently produces garbled text:

rust,no_run
use liteparse::ocr::oar::OarOcrEngine;

let engine = OarOcrEngine::from_models(
    "pp-ocrv6_small_det.onnx",
    "pp-ocrv6_small_rec.onnx",
    "ppocrv6_dict.txt",
)?;
# Ok::<(), Box<dyn std::error::Error>>(())

All three artifacts also accept in-memory bytes, so the whole pipeline can be
embedded with include_bytes! rather than shipped as files. Use OAROCRBuilder
with OarOcrEngine::from_builder for model-specific settings or optional
orientation and rectification models. The fallible constructors return
liteparse::LiteParseError. All constructors use conservative batch sizes and
serialize page inference to avoid multiplying inference memory across
concurrently scheduled pages.
OcrOptions::language is not interpreted: the recognition model and character
dictionary define the supported languages, and a configured ocr_language
triggers a one-time warning to make that explicit.

Features

  • tesseract (default) — Built-in Tesseract OCR via tesseract-rs. Disable with default-features = false if you don't need OCR or want to use an HTTP OCR server instead.
  • oar-ocr — Optional native ONNX backend via oar-ocr. Local or in-memory models; non-WASM Rust API only.
  • oar-ocr-auto-download — Enables SHA-256-verified download and caching of registered model file names through oar-ocr.
  • oar-ocr-cuda, oar-ocr-tensorrt, oar-ocr-directml, oar-ocr-coreml, oar-ocr-webgpu, oar-ocr-openvino — Forward the selected ONNX Runtime execution provider to oar-ocr.

The Node.js, Python, and WASM bindings build LiteParse with default features
disabled and do not expose these OAR features, so their published binaries do
not inherit the OAR model or runtime dependency footprint.

Supported Formats

  • PDF (.pdf)
  • Microsoft Office (.docx, .xlsx, .pptx, etc.) — requires LibreOffice
  • OpenDocument (.odt, .ods, .odp) — requires LibreOffice
  • Images (.png, .jpg, .tiff, etc.)

CLI

The crate also builds the lit CLI binary:

bash
lit parse document.pdf
lit parse document.pdf --format json -o output.json
lit parse document.pdf --format markdown -o output.md
lit screenshot document.pdf -o ./screenshots
lit batch-parse ./input ./output
lit is-complex document.pdf

See lit --help for all options.

License

Apache-2.0


Overview (`ocr/easyocr/README.md`)

EasyOCR Service

This is a simple Flask server that wraps EasyOCR to conform to the LiteParse OCR API specification (see ../../OCR_API_SPEC.md).

Build and Run

bash
# install and run (in one command)
uv run server.py

Usage

The service exposes a single endpoint:

  • POST /ocr - Perform OCR on an uploaded image

Parameters

  • file - Image file (multipart/form-data)
  • language - Language code (e.g., 'en', 'fr', 'de')

Example

bash
curl -X POST -F "[email protected]" -F "language=en" http://localhost:8828/ocr

Response Format

json
{
  "results": [
    {
      "text": "recognized text",
      "bbox": [x1, y1, x2, y2],
      "confidence": 0.95
    }
  ]
}

This conforms to the LiteParse OCR API specification.

Supported Languages

EasyOCR supports 80+ languages. Common language codes:

  • en - English
  • fr - French
  • de - German
  • es - Spanish
  • zh - Chinese
  • ja - Japanese
  • ko - Korean
  • ar - Arabic

Full list: https://www.jaided.ai/easyocr/

Use with LiteParse

Once the server is running, use it with LiteParse OSS:

bash
# Parse with EasyOCR
lit parse document.pdf --ocr-server-url http://localhost:8828/ocr

# With specific language
lit parse document.pdf --ocr-server-url http://localhost:8828/ocr --ocr-language zh

Or in code:

typescript
import { LiteParse } from 'liteparse';

const parser = new LiteParse({
  ocrServerUrl: 'http://localhost:8828/ocr',
  ocrLanguage: 'en',
});

const result = await parser.parse('document.pdf');

Testing

If you make changes to the server, make sure to adapt and run tests:

bash
uv run pytest test_server.py

Overview (`ocr/paddleocr/README.md`)

PaddleOCR Service

This is a simple Flask server that wraps PaddleOCR to conform to the LiteParse OCR API specification (see ../../OCR_API_SPEC.md).

PaddleOCR is especially fast and accurate for Chinese, Japanese, and Korean languages.

Build and Run

bash
# install and run (in one command)
uv run server.py

Usage

The service exposes a single endpoint:

  • POST /ocr - Perform OCR on an uploaded image

Parameters

  • file - Image file (multipart/form-data)
  • language - Language code (e.g., 'en', 'zh', 'ja', 'ko')

Example

bash
curl -X POST -F "[email protected]" -F "language=zh" http://localhost:8829/ocr

Response Format

json
{
  "results": [
    {
      "text": "recognized text",
      "bbox": [x1, y1, x2, y2],
      "confidence": 0.95
    }
  ]
}

This conforms to the LiteParse OCR API specification.

Supported Languages

PaddleOCR supports 80+ languages with excellent support for CJK:

  • en - English
  • zh / zh-cn - Chinese (Simplified)
  • zh-tw / zh-hant - Chinese (Traditional)
  • ja - Japanese
  • ko - Korean
  • fr - French
  • de - German
  • es - Spanish
  • pt - Portuguese
  • ru - Russian
  • ar - Arabic
  • hi - Hindi/Devanagari

Full list: https://github.com/PaddlePaddle/PaddleOCR

Performance

PaddleOCR is optimized for speed and accuracy:

  • Fast: 2-3x faster than EasyOCR
  • Accurate: Especially for Asian languages (Chinese, Japanese, Korean)
  • Lightweight: Smaller model sizes

Use with LiteParse

Once the server is running, use it with LiteParse:

bash
# Parse with PaddleOCR
lit parse document.pdf --ocr-server-url http://localhost:8829/ocr

# With specific language
lit parse document.pdf --ocr-server-url http://localhost:8829/ocr --ocr-language zh

Or in code:

typescript
import { LiteParse } from 'liteparse';

const parser = new LiteParse({
  ocrServerUrl: 'http://localhost:8829/ocr',
  ocrLanguage: 'zh',
});

const result = await parser.parse('document.pdf');

Testing

If you make changes to the server, make sure to adapt and run tests:

bash
uv run pytest test_server.py

GPU Support

For GPU acceleration, set use_gpu=True in server.py.

Notes

  • First request may be slow as PaddleOCR downloads models
  • Models are cached after first use
  • Default port is 8829 (different from EasyOCR's 8828)

Overview (`ocr/suryaocr/README.md`)

Surya OCR Service

A FastAPI server wrapping Surya OCR 2 to
conform to the LiteParse OCR API specification (see ../../OCR_API_SPEC.md).

Surya 2 is a multilingual OCR foundation model with strong accuracy across many
languages — a single model handles all languages with no per-language setup.

Build and Run

bash
# install and run (in one command)
uv run server.py

The first run downloads Surya model weights from Hugging Face and may take a few
minutes; weights are cached afterward.

Inference backend (required)

Surya 2 is a VLM-backed model: text recognition runs through a separate
inference backend, which you must provide. Surya does not bundle it.

  • CPU / local (llama.cpp): install the llama-server binary so Surya's
    llamacpp backend can spawn it:
  • GPU (vllm): set SURYA_INFERENCE_BACKEND=vllm (requires a CUDA GPU).
  • External server: point SURYA_INFERENCE_URL at an already-running Surya
    inference server to attach without spawning one locally.

Without a backend, startup succeeds and GET /health works, but POST /ocr
returns a 500 with "llama-server binary not found".

Docker (GPU, single image)

The provided Dockerfile builds one image that runs both the API and the
model server on a GPU
. It is based on the official CUDA llama.cpp image
(ghcr.io/ggml-org/llama.cpp:server-cuda), which ships a qwen35-capable
llama-server (Surya 2's custom GGUF architecture) with libggml-cuda and the
CUDA runtime. Surya spawns llama-server from PATH inside the same container,
so no sidecar or separate inference service is needed.

bash
# Build
docker build -t suryaocr-liteparse:cuda .

# Run (requires the NVIDIA container runtime)
docker run --rm --gpus all -p 8830:8830 \
  -v "$HOME/.cache/suryaocr-models:/models" \
  suryaocr-liteparse:cuda
  • --gpus all exposes the host GPUs; all model layers are offloaded
    (LLAMA_CPP_NGL=99).
  • The -v …:/models mount caches weights (HF_HOME=/models). The first
    POST /ocr downloads the GGUF weights from Hugging Face and spawns
    llama-server (a few minutes); subsequent requests reuse the cache and the
    running server.

The image bakes in the backend configuration: SURYA_INFERENCE_BACKEND=llamacpp,
LLAMA_CPP_NGL=99, and SURYA_GUIDED_LAYOUT=false. The last one is required on
the upstream llama-server build: Surya's layout step is the only request that
sends a grammar, and its regex pattern breaks llama.cpp's
json-schema→GBNF converter (failed to parse grammar), which otherwise yields
empty results. With guided layout off, layout runs as free generation (Surya
parses the output itself) and the rest of the pipeline never uses a grammar.

To use a CUDA GPU via vLLM instead of bundled llama.cpp, set
SURYA_INFERENCE_BACKEND=vllm (see the backend section above).

Verify it is up:

bash
curl http://localhost:8830/health
curl -X POST -F "[email protected]" http://localhost:8830/ocr

Usage

The service exposes:

  • POST /ocr — Perform OCR on an uploaded image
  • GET /health — Health check

Parameters

  • file — Image file (multipart/form-data)
  • language — Language code (accepted for API compatibility; ignored, since
    Surya 2 is multilingual)

Example

bash
curl -X POST -F "[email protected]" http://localhost:8830/ocr

Response Format

json
{
  "results": [
    {
      "text": "recognized text",
      "bbox": [x1, y1, x2, y2],
      "confidence": 0.95,
      "polygon": [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]
    }
  ]
}

Results are block-level (one entry per detected layout block), with each
block's HTML stripped to plain text. This conforms to the LiteParse OCR API spec.

Supported Languages

Surya 2 is a single multilingual model — no language parameter is required
(the language field is accepted but ignored).

Per Surya's own benchmark, it scores an 87.2% overall pass rate across 91
languages
, with 38 of the 91 languages scoring ≥ 90% and 76 scoring ≥ 80%,
covering text accuracy, layout, tables, math, and reading order. It has strong
performance across both Latin and non-Latin scripts.

Device / GPU

There are two device knobs, for two different parts of the pipeline:

  • LLAMA_CPP_NGL controls how many layers of the VLM (the model that reads
    text) the llamacpp backend offloads to the GPU. 99 offloads everything;
    0 keeps it on CPU. The Docker image sets 99.

  • TORCH_DEVICE controls the device for Surya's helper torch models
    (e.g. layout). Surya auto-detects, or you can force it:

    bash
    TORCH_DEVICE=cuda uv run server.py   # GPU
    TORCH_DEVICE=cpu  uv run server.py   # CPU

For a turnkey GPU deployment, prefer the Docker image above — it wires both up.

Use with LiteParse

bash
lit parse document.pdf --ocr-server-url http://localhost:8830/ocr

Or in code:

typescript
import { LiteParse } from 'liteparse';

const parser = new LiteParse({
  ocrServerUrl: 'http://localhost:8830/ocr',
});

const result = await parser.parse('document.pdf');

Testing

bash
uv run pytest test_server.py

Tests mock the Surya predictor, so they run without downloading any models.

Notes

  • First request/startup may be slow while models download.
  • Default port is 8830 (easyocr 8828, paddleocr 8829).
  • Output is block-granular; Surya 2 has no per-line text API.

Overview (`packages/node/README.md`)

LiteParse Node.js

Node.js/TypeScript bindings for LiteParse — fast, lightweight PDF and document parsing with spatial text extraction.

Installation

bash
npm i @llamaindex/liteparse

This also installs the lit CLI command (use npm i -g for global access).

Quick Start

typescript
import { LiteParse } from '@llamaindex/liteparse';

const parser = new LiteParse();
const result = await parser.parse('document.pdf');
console.log(result.text);
console.log(`Source document pages: ${result.totalPages}`);

// Access structured data
for (const page of result.pages) {
  console.log(`Page ${page.pageNum}: ${page.textItems.length} text items`);
}

CommonJS is supported as well:

javascript
const { LiteParse } = require('@llamaindex/liteparse');

(async () => {
  const parser = new LiteParse();
  const result = await parser.parse('document.pdf');
  console.log(result.text);
})();

Bounded-memory parsing

For documents with many text items, consume page batches without retaining
earlier results:

typescript
const parser = new LiteParse();
for await (const batch of parser.parseBatches('large.pdf', { batchSize: 20 })) {
  await processPages(batch.result.pages);
}

Each batch is an ordinary parse result covering batch.startPage through
batch.endPage, and becomes collectible as soon as you advance the iterator.
A non-PDF source is converted once, not once per batch.

Cross-page passes only see the pages in their own batch, so repeated
header/footer removal and image deduplication are batch-local and the output
can differ from parse(). Prefer parse() unless the size of the
materialized result is the problem.

Markdown Output

LiteParse can render documents directly to Markdown including headings, tables, lists,
images, and links reconstructed from the spatial layout. Great for feeding LLMs
and RAG pipelines. The rendered Markdown is returned on result.text:

typescript
const parser = new LiteParse({
  outputFormat: 'markdown',     // "json" | "text" | "markdown"
  imageMode: 'placeholder',     // "placeholder" | "off" | "embed"
  extractLinks: true,           // render [text](url) link syntax (default: true)
});
const result = await parser.parse('document.pdf');
console.log(result.text); // rendered Markdown

Reconstruction quality varies with document complexity.

Configuration

All options are passed to the constructor:

text
/* Detailed source-code truncated for AI context efficiency. */

When extractImages is true, image extraction is enabled. imageOutputDir requires
that explicit opt-in and writes the extracted bytes to disk. Each
result.images entry includes its page bbox, intrinsic pixel dimensions, rotation,
format, name, and path. Valid source JPEGs are preserved, exact duplicates reuse
one file, and JSON CLI output contains metadata only (no base64 image data).
imageMode controls Markdown presentation only and does not imply extraction. With
extractImages: false, lightweight Markdown placement refs are still collected and
result.images stays empty.

The Node CLI uses the same snake_case JSON schema as the Rust CLI; camelCase is
reserved for the programmatic Node API.

When extractAnnotations is enabled, each parsed page has an annotations
array containing the subtype, contents, author/title, PDF date strings,
viewport-space rectangle and quadpoint rectangles, and URI for external link
annotations. It is independent of extractLinks, which controls Markdown link
rendering. The field is omitted when extraction is disabled.

When extractStructureTree is enabled, each page has a structureTree containing
all tagged-PDF roots and recursive elements with type, ID, actual/alternate text,
title, typed attributes, MCIDs, children, and referenced link annotations. Untagged
pages have an empty roots array; the field is omitted when disabled.

Every result also carries the document's /Info creator/producer when
present. With extractDocumentMetadata, result.docMeta adds a provenance
object with dates, PDF version/security, signature state, incremental-save
markers, trailer ID comparison, the catalog's XMP packet (capped at 64 KiB;
skipped for sources over 16 MiB), and source size — off by default since it
streams the whole file, and absent for inputs converted from a non-PDF
format. These are API-level only, not in CLI JSON. With extractContentBounds
each page carries a contentBounds union bbox of its top-level content
objects. With extractXfaPackets, result.xfaPackets lists each raw XFA
packet (index, name, content length, XML content); non-XFA documents yield an
empty array. Screenshots draw form-field appearances into the raster and
report isSolidFill per page, plus detected solid rects when
detectScreenshotRects is enabled.

Parsing from Bytes

Pass a Buffer or Uint8Array directly — useful for HTTP responses or in-memory data:

typescript
import { readFile } from 'fs/promises';

const pdfBytes = await readFile('document.pdf');
const result = await parser.parse(pdfBytes);
console.log(result.text);

Worker Pool and Hard Timeouts

PDFium is not thread-safe, so all parses inside one process serialize on a
process-global lock — even Promise.all over multiple parse() calls runs
them one at a time.

For high-throughput services, or for cases when you need to enforce a
runtime timeout, run parses in a pool of persistent worker processes instead:

typescript
import { LiteParse, ParseTimeoutError } from '@llamaindex/liteparse';

const parser = new LiteParse({ poolSize: 4, parseTimeoutMs: 15_000 });
await parser.warmUp(); // optional: pre-initialize workers (~45ms total)

try {
  const result = await parser.parse('document.pdf');
} catch (e) {
  if (e instanceof ParseTimeoutError) {
    console.warn(`killed rogue document: ${e.source} (deadline ${e.timeoutMs}ms)`);
  }
}

parser.close(); // frees workers immediately; an idle pool never blocks exit

Screenshots

Generate PNG screenshots of document pages:

typescript
const screenshots = parser.screenshot('document.pdf', [1, 2, 3]);
for (const s of screenshots) {
  console.log(`Page ${s.pageNum}: ${s.width}x${s.height}`);
  // s.imageBuffer contains PNG bytes
}

Document Complexity

Before committing to a full parse, check whether a document needs OCR or heavier
processing. isComplex is a cheap, text-layer-only pass that returns one entry per page
with a needsOcr verdict and the signals behind it — useful for routing documents to
different pipelines, rejecting ones you can't handle, or estimating cost.

typescript
const parser = new LiteParse();
const pages = await parser.isComplex('document.pdf');

if (pages.some((p) => p.needsOcr)) {
  // Route to the OCR-enabled pipeline
  const result = await parser.parse('document.pdf');
} else {
  // Cheap path — skip OCR entirely
  const result = await new LiteParse({ ocrEnabled: false }).parse('document.pdf');
}

// Inspect why specific pages were flagged
for (const page of pages.filter((p) => p.needsOcr)) {
  console.log(`Page ${page.pageNumber}: ${page.reasons.join(', ')}`);
}

reasons is one of "scanned", "no-text", "sparse-text", "embedded-images",
"garbled", "vector-text", or "annotation-text". Raw bytes work here too.

Supported Formats

  • PDF (.pdf)
  • Microsoft Office (.docx, .xlsx, .pptx, etc.) — requires LibreOffice
  • OpenDocument (.odt, .ods, .odp) — requires LibreOffice
  • Images (.png, .jpg, .tiff, etc.)
  • And more!

CLI

The npm package includes the lit CLI:

bash
lit parse document.pdf
lit parse document.pdf --format json -o output.json
lit parse document.pdf --format json --extract-annotations
lit parse document.pdf --format json --extract-form-fields
lit screenshot document.pdf -o ./screenshots
lit batch-parse ./input ./output
lit is-complex document.pdf

Overview (`packages/python/README.md`)

LiteParse Python

Python bindings for LiteParse — fast, lightweight PDF and document parsing with spatial text extraction.

Installation

bash
pip install liteparse

This also installs the lit CLI command.

Quick Start

python
from liteparse import LiteParse

parser = LiteParse()
result = parser.parse("document.pdf")
print(result.text)
print(f"Source document pages: {result.total_pages}")

# Access structured data
for page in result.pages:
    print(f"Page {page.page_num}: {len(page.text_items)} text items")

Markdown Output

LiteParse can render documents directly to Markdown including headings, tables, lists,
images, and links reconstructed from the spatial layout. Great for feeding LLMs
and RAG pipelines. The rendered Markdown is returned on result.text:

python
parser = LiteParse(
    output_format="markdown",   # "json" | "text" | "markdown"
    image_mode="placeholder",   # "placeholder" | "off" | "embed"
    extract_links=True,         # render [text](url) link syntax (default: True)
)
result = parser.parse("document.pdf")
print(result.text)  # rendered Markdown

Reconstruction quality varies with document complexity.

Configuration

All options are passed to the constructor:

text
/* Detailed source-code truncated for AI context efficiency. */

When extract_images=True, image extraction is enabled. image_output_dir
requires that explicit opt-in and writes the extracted bytes to disk. Each
result.images entry includes its page bbox, intrinsic pixel dimensions, rotation,
format, name, and path. Valid source JPEGs are preserved, exact duplicates
reuse one file, and JSON CLI output contains metadata only (no base64 image data).
image_mode controls Markdown presentation only and does not imply extraction.
With extract_images=False, lightweight Markdown placement refs are still collected
and result.images stays empty.

When extract_annotations is enabled, each parsed page has an annotations
list containing the subtype, contents, author/title, PDF date strings,
viewport-space rectangle and quadpoint rectangles, and URI for external link
annotations. It is independent of extract_links, which controls Markdown
link rendering. The field is None when extraction is disabled.

When extract_structure_tree=True, each page has a structure_tree containing
all tagged-PDF roots and recursive elements with type, ID, actual/alternate text,
title, typed attributes, MCIDs, children, and referenced link annotations. Untagged
pages have an empty roots list; the field is None when disabled.

Every result also carries creator/producer from the PDF /Info
dictionary. With extract_document_metadata=True, result.doc_meta adds a
provenance object with dates, PDF version/security, signature state,
incremental-save markers, trailer ID comparison, the catalog's XMP packet
(capped at 64 KiB; skipped for sources over 16 MiB), and source size. It is off by default because it streams the whole source file,
and it is None for inputs converted from a non-PDF format. These document
fields are API-only and do not alter default CLI JSON.

Parsing from Bytes

Pass raw PDF bytes directly — useful for web uploads or downloaded files:

python
with open("document.pdf", "rb") as f:
    result = parser.parse(f.read())
print(result.text)

Worker Pool and Hard Timeouts

PDFium is not thread-safe, so in-process parses serialize on a process-global
lock. For high-throughput services, or cases where you need to enforce a timeout,
run parses in a pool of persistent worker processes instead:

python
from liteparse import LiteParse, ParseTimeoutError

parser = LiteParse(pool_size=4, parse_timeout=15)
parser.warm_up()  # optional: pre-initialize workers (~60ms total)

try:
    result = parser.parse("document.pdf")
except ParseTimeoutError as e:
    print(f"killed rogue document: {e.source} (deadline {e.timeout}s)")

parser.close()  # or use `with LiteParse(...) as parser:`

Screenshots

Generate PNG screenshots of document pages:

python
screenshots = parser.screenshot("document.pdf", page_numbers=[1, 2, 3])
for s in screenshots:
    print(f"Page {s.page_num}: {s.width}x{s.height}")
    with open(f"page_{s.page_num}.png", "wb") as f:
        f.write(s.image_bytes)

Document Complexity

Before committing to a full parse, check whether a document needs OCR or heavier
processing. is_complex is a cheap, text-layer-only pass that returns one entry per page
with a needs_ocr verdict and the signals behind it — useful for routing documents to
different pipelines, rejecting ones you can't handle, or estimating cost.

python
parser = LiteParse()
pages = parser.is_complex("document.pdf")

if any(p.needs_ocr for p in pages):
    # Route to the OCR-enabled pipeline
    result = parser.parse("document.pdf")
else:
    # Cheap path — skip OCR entirely
    result = LiteParse(ocr_enabled=False).parse("document.pdf")

# Inspect why specific pages were flagged
for page in pages:
    if page.needs_ocr:
        print(f"Page {page.page_number}: {', '.join(page.reasons)}")

reasons is one of "scanned", "no-text", "sparse-text", "embedded-images",
"garbled", "vector-text", or "annotation-text". Raw bytes work here too.

Supported Formats

  • PDF (.pdf)
  • Microsoft Office (.docx, .xlsx, .pptx, etc.) — requires LibreOffice
  • OpenDocument (.odt, .ods, .odp) — requires LibreOffice
  • Images (.png, .jpg, .tiff, etc.)
  • And more!

CLI

The Python package includes the lit CLI:

bash
lit parse document.pdf
lit parse document.pdf --format json -o output.json
lit parse document.pdf --format json --extract-annotations
lit parse document.pdf --format json --extract-form-fields
lit screenshot document.pdf -o ./screenshots
lit batch-parse ./input ./output
lit is-complex document.pdf

Overview (`packages/wasm/README.md`)

@llamaindex/liteparse-wasm

Browser/WebAssembly build of LiteParse — a fast, lightweight PDF parser with spatial text extraction.

This package runs entirely in the browser. No server, no cloud calls.

Install

sh
npm install @llamaindex/liteparse-wasm

Quick start

ts
import init, { LiteParse } from "@llamaindex/liteparse-wasm";

// Load the wasm module (point at the file shipped with the package).
await init();

const parser = new LiteParse({
  ocrEnabled: false, // OCR requires a JS-side engine (see below)
  outputFormat: "json",
});

// `data` is a Uint8Array (e.g. from fetch / File / drag-drop).
const bytes = new Uint8Array(await file.arrayBuffer());
const result = await parser.parse(bytes);

console.log(result.text);          // full document text
console.log(result.pages[0]);      // per-page items with bboxes

Document complexity

Before committing to a full parse, check whether a document needs OCR or heavier
processing. isComplex is a cheap, text-layer-only pass that returns one entry per page
with a needsOcr verdict and the signals behind it — useful for routing documents or
deciding whether the JS-side OCR engine is worth wiring up.

ts
const parser = new LiteParse({ ocrEnabled: false });
const bytes = new Uint8Array(await file.arrayBuffer());
const pages = await parser.isComplex(bytes);

if (pages.some((p) => p.needsOcr)) {
  // This document would benefit from OCR — see "OCR in the browser" below
  for (const page of pages.filter((p) => p.needsOcr)) {
    console.log(`Page ${page.pageNumber}: ${page.reasons.join(", ")}`);
  }
}

reasons is one of "scanned", "no-text", "sparse-text", "embedded-images",
"garbled", "vector-text", or "annotation-text".

Config options

All optional, camelCase:

Option Type Default Description
ocrLanguage string "eng" Language code passed to the OCR engine
ocrEnabled boolean false Run OCR on text-sparse pages
maxPages number 1000 Stop after this many pages
targetPages string — e.g. "1-5,10,15-20"
extractScreenshots boolean false Return parsed pages as PNG bytes on result.screenshots
dpi number 150 Render DPI for OCR / screenshots
outputFormat "json" | "text" | "markdown" "json" Output format; "markdown" returns rendered Markdown on result.text
imageMode "off" | "placeholder" | "embed" "placeholder" How raster images are surfaced in markdown output
extractLinks boolean true Render hyperlink annotations as [text](url) in markdown output
extractVectorGraphics boolean false Include page-scoped shapes and merged horizontal/vertical lines
extractAnnotations boolean false Include page annotations and their metadata/geometry in structured output
extractStructureTree boolean false Include the tagged-PDF logical structure tree
preserveVerySmallText boolean false Keep tiny text that's normally filtered
password string — Password for protected PDFs
quiet boolean false Suppress progress logging
numWorkers number 1 Maximum concurrent OCR calls
ocrEngine object — JS-side OCR engine (see below)

OCR in the browser

The native HTTP-OCR and Tesseract backends are not available in the browser.
To use OCR, set ocrEnabled: true and supply an ocrEngine object with a
recognize method. Supplying ocrEngine alone does not enable OCR.

ts
const parser = new LiteParse({
  ocrEnabled: true,
  ocrLanguage: "eng",
  // Use more than 1 only if the engine supports concurrent jobs.
  numWorkers: 2,
  ocrEngine: {
    /**
     * @param imageData PNG-encoded image bytes
     * @param width  rendered page width  in pixels
     * @param height rendered page height in pixels
     * @param language e.g. "eng"
     * @returns array of { text, bbox: [x1,y1,x2,y2], confidence }
     */
    async recognize(imageData, width, height, language) {
      // e.g. call a worker that wraps tesseract.js, or a remote OCR service
      return [
        { text: "Hello", bbox: [10, 20, 80, 40], confidence: 0.98 },
      ];
    },
  },
});

LiteParse keeps at most numWorkers calls to recognize active. When one call
finishes, LiteParse prepares and starts the next page without waiting for the
other calls. An OCR engine can send the calls to a Web Worker pool or to
concurrent HTTP requests. Results stay assigned to their source pages when
jobs finish out of order. Active calls can keep up to numWorkers rendered
page rasters in memory, so use a small value for high-DPI documents.

A failed recognize call only affects its own page. With ocrFailureFatal: true (the default), parse() rejects only when every OCR call failed and at
least one failed page had little native text. If parsing fails, OCR calls that
have already started can continue.

Building from source

Requires Rust + wasm-pack:

sh
# from packages/wasm
npm run build           # web target (default)
npm run build:bundler   # for webpack/rollup/vite
npm run build:nodejs    # for node.js

Output goes to pkg/.

License

Apache-2.0


Src Content Docs Liteparse Index (`docs/src/content/docs/liteparse/index.md`)


title: What is LiteParse? sidebar: order: 0

LiteParse is an open-source document parsing library that parses text with spatial layout information and bounding boxes. Written in Rust for speed and reliability, it runs entirely on your machine with no cloud dependencies, no LLMs, and no API keys.

LiteParse is designed specifically for use cases that require fast, accurate text parsing: real-time applications, coding agents, and local workflows. It provides a simple CLI and library API for parsing PDFs, Office documents, and images, with built-in OCR support.

What can LiteParse do?

  • Parse PDFs with precise spatial layout. Text comes back positioned where it appears on the page
  • Render to Markdown with headings, tables, lists, images, and links — clean structured output for LLMs and RAG pipelines
  • Extract bounding boxes for every text line, ready for downstream processing or visualization
  • OCR scanned documents using built-in Tesseract or plug in your own OCR server
  • Parse Office files and images with support for DOCX, XLSX, PPTX, PNG, JPG, and more via automatic conversion
  • Screenshot PDF pages as high-quality images for LLM-based workflows
  • Pull structured PDF data — embedded images, vector graphics, annotations, AcroForm fields, and tagged-structure trees
  • Score document complexity before you parse, so you can route scanned or multi-column documents to the right pipeline
  • Use from Node.js/TypeScript, Python, Rust, or the browser (WASM) — whatever fits your stack

Get started

  • Getting started: Install LiteParse and parse your first document.
  • Markdown output: Render documents to clean, structured Markdown.
  • Extraction options: Opt in to images, form fields, annotations, and more.
  • Document complexity: Detect scanned, multi-column, and table-heavy pages up front.
  • Library usage: Use LiteParse from TypeScript or Python code.
  • Browser usage (WASM): Run LiteParse in the browser with zero server dependencies.
  • Agent skill: Give Claude Code, Cursor, or Codex the ability to parse documents locally.
  • CLI reference: Complete command and option reference.
  • API reference: Detailed API documentation (rust) for all public types and functions. The same types apply across all language bindings.

Src Content Docs Liteparse Cli Reference (`docs/src/content/docs/liteparse/cli-reference.md`)


title: CLI Reference sidebar: order: 5

LiteParse provides the lit CLI with four commands: parse, batch-parse, screenshot, and is-complex. The CLI is the same whether installed via npm, pip, or built from Rust source.

`lit parse`

Parse a single document.

text
lit parse [options] <file>

Arguments

Argument Description
file Path to the document file, or - to read from stdin

Options

Option Description Default
-o, --output <file> Write output to a file instead of stdout —
--format <format> Output format: json, text, or markdown text
--image-mode <mode> Markdown image handling: off, placeholder, or embed placeholder
--image-output-dir <dir> Directory to write extracted images to. Requires --extract-images (or --image-mode embed, which implies it) —
--no-links Emit link anchor text as plain text (no [text](url)) in markdown —
--keep-headers-footers Keep running header/footer chrome in markdown instead of stripping it —
--no-ocr Disable OCR entirely —
--ocr-language <lang> OCR language code (Tesseract format) eng
--ocr-server-url HTTP OCR server URL — (uses Tesseract)
--ocr-server-header <header> Extra HTTP header for OCR server requests, as "Name: Value". Repeatable —
--tessdata-path Path to tessdata directory — (uses TESSDATA_PREFIX env var)
--num-workers <n> Pages to OCR in parallel CPU cores - 1
--max-pages <n> Maximum pages to parse 1000
--target-pages Pages to parse (e.g., "1-5,10") — (all pages)
--dpi <dpi> Rendering DPI 150
--preserve-small-text Keep very small text —
--password Password for encrypted/protected documents —
-q, --quiet Suppress progress output —

Extraction options

By default, JSON output contains only text, geometry, and page metadata. Each option below is off by default and adds a new key to the JSON output. Enable only what you need — every one of them costs parse time and output size.

Option Adds to JSON Description
--extract-images images[], image_error_count Extract embedded raster image bytes and metadata. Pair with --image-output-dir to write files to disk
--extract-vector-graphics pages[].vector_graphics Vector shapes and lines ({ shapes, lines }) with bounding boxes, stroke/fill colors
--extract-annotations pages[].annotations PDF annotations: subtype, contents, timestamps, rects, link URIs
--extract-form-fields pages[].form_fields AcroForm widgets: type, name, value, flags, options, rect
--extract-structure-tree pages[].structure_tree Tagged-PDF logical structure tree ({ roots }), including per-element attributes
--extract-blocks pages[].blocks Classified layout blocks in reading order (headings, paragraphs, list items, tables with per-cell boxes, code, rules, figures), each with a bounding box
--extract-content-bounds pages[].content_bounds Bounding box of actual content on the page, as a Rect
--extract-xfa-packets xfa_packets[] (document level) Raw XFA packets from XFA-based forms
--extract-text-metadata extra keys on each text_items[] entry Rich per-item typography: font_height, font_ascent, font_descent, font_weight, text_width, font_is_buggy, mcid, fill_color, stroke_color, plus rotation
--complexity pages[].complexity Per-page complexity signals, including the nested layout object. See the Document Complexity guide

Examples

bash
# Basic text parsing
lit parse report.pdf

# JSON output with bounding boxes
lit parse report.pdf --format json -o report.json

# Markdown output (headings, tables, lists, images, links)
lit parse report.pdf --format markdown -o report.md

# Markdown with extracted images written to disk
lit parse report.pdf --format markdown --extract-images --image-output-dir ./images

# Pull form fields and annotations out of a filled-in PDF
lit parse form.pdf --format json --extract-form-fields --extract-annotations

# Rich typography metadata on every text item
lit parse report.pdf --format json --extract-text-metadata

# Inline complexity signals (including layout) alongside the parse
lit parse report.pdf --format json --complexity

# Authenticated OCR server
lit parse scan.pdf --ocr-server-url https://ocr.internal/ocr \
  --ocr-server-header "Authorization: Bearer $TOKEN"

# Parse pages 1-5 only, no OCR
lit parse report.pdf --target-pages "1-5" --no-ocr

# High-DPI rendering with French OCR
lit parse report.pdf --dpi 300 --ocr-language fra

# Use an external OCR server
lit parse report.pdf --ocr-server-url http://localhost:8828/ocr

# Pipe output to another tool
lit parse report.pdf -q | wc -l

# Parse a remote file via stdin
curl -sL https://example.com/report.pdf | lit parse --no-ocr -

`lit batch-parse`

Parse multiple documents in a directory.

text
lit batch-parse [options] <input-dir> <output-dir>

Arguments

Argument Description
input-dir Directory containing documents to parse
output-dir Directory for output files

Options

Option Description Default
--format <format> Output format: json, text, or markdown text
--no-ocr Disable OCR entirely —
--ocr-language <lang> OCR language code eng
--ocr-server-url HTTP OCR server URL — (uses Tesseract)
--ocr-server-header <header> Extra HTTP header for OCR server requests, as "Name: Value". Repeatable —
--tessdata-path Path to tessdata directory —
--num-workers <n> Pages to OCR in parallel CPU cores - 1
--max-pages <n> Maximum pages per file 1000
--dpi <dpi> Rendering DPI 150
--recursive Search subdirectories —
--extension <ext> Only process this extension (e.g., ".pdf") — (all supported)
--password Password for encrypted/protected documents (applied to all files) —
-q, --quiet Suppress progress output —

batch-parse accepts the same extraction options as lit parse: --extract-images, --extract-vector-graphics, --extract-annotations, --extract-form-fields, --extract-structure-tree, --extract-blocks, --extract-content-bounds, --extract-xfa-packets, --extract-text-metadata, and --complexity.

It does not accept --target-pages, --preserve-small-text, --image-mode, --image-output-dir, --no-links, or --keep-headers-footers. Use lit parse per file when you need those.

Examples

bash
# Parse all supported files in a directory
lit batch-parse ./documents ./output

# Recursively parse only PDFs
lit batch-parse ./documents ./output --recursive --extension ".pdf"

# Batch parse with JSON output and no OCR
lit batch-parse ./documents ./output --format json --no-ocr

`lit screenshot`

Generate page images from a document (PDF, DOCX, XLSX, images, etc.).

text
lit screenshot [options] <file>

Arguments

Argument Description
file Path to the document file

Options

Option Description Default
-o, --output-dir <dir> Output directory ./screenshots
--target-pages Pages to screenshot (e.g., "1,3,5" or "1-5") — (all pages)
--dpi <dpi> Rendering DPI 150
--password Password for encrypted/protected documents —
-q, --quiet Suppress progress output —

Examples

bash
# Screenshot all pages of a PDF
lit screenshot document.pdf -o ./pages

# Screenshot a Word document (requires LibreOffice)
lit screenshot report.docx -o ./pages

# First 5 pages at high DPI
lit screenshot document.pdf --target-pages "1-5" --dpi 300 -o ./pages

# Specific pages only
lit screenshot document.pdf --target-pages "1,5,10" -o ./pages

`lit is-complex`

Check whether a document needs OCR or heavier parsing — a cheap pre-parse pass over the text layer only (no rasterization, no OCR). See the Document Complexity guide for details.

text
lit is-complex [options] <file>

The command prints per-page JSON to stdout, a human-readable verdict to stderr, and exits non-zero when any page needs OCR — so it works as a shell predicate or a jq source.

Arguments

Argument Description
file Path to the document file

Options

Option Description Default
--compact Emit dense, whitespace-free JSON instead of pretty-printed —
--max-pages <n> Maximum pages to check 1000
--target-pages Pages to check (e.g., "1-5,10,15-20") — (all pages)
--password Password for encrypted/protected documents —
-q, --quiet Suppress the stderr verdict —

Examples

bash
# Print the complexity verdict and per-page JSON
lit is-complex document.pdf

# Use as a shell predicate: only parse with --no-ocr when simple
lit is-complex document.pdf --quiet && lit parse document.pdf --no-ocr

# List the page numbers that need OCR
lit is-complex document.pdf --compact | jq '[.[] | select(.needs_ocr) | .page_number]'

Global options

These options are available on all commands:

Option Description
-h, --help Show help for a command
-V, --version Show version number

Src Content Docs Liteparse Getting Started (`docs/src/content/docs/liteparse/getting_started.mdx`)


title: Getting Started sidebar: order: 1

LiteParse is available for Node.js, Python, and as a standalone Rust binary.

All versions (except WASM) ship the same CLI and core library capabilities.

Installation

bash
npm i @llamaindex/liteparse
bash
pip install liteparse
bash
cargo install liteparse
bash
npm i @llamaindex/liteparse-wasm

Since the WASM package is designed for browser usage, it does not include the lit CLI command. Instead, you can use the exported functions directly from JavaScript to parse documents in the browser environment.

For full browser usage and limitations, see the WASM package guide.

Quick start

Once installed, parse from the command line:

bash
# Parse a PDF and print text to stdout
lit parse document.pdf

# Save output to a file
lit parse document.pdf -o output.txt

# Get structured JSON with bounding boxes
lit parse document.pdf --format json -o output.json

# Render to Markdown (headings, tables, lists, images, links)
lit parse document.pdf --format markdown -o output.md

# Parse only specific pages
lit parse document.pdf --target-pages "1-5,10,15-20"

Batch parsing

Parse an entire directory of documents at once:

bash
lit batch-parse ./pdfs ./outputs

Screenshots

Generate page images from a PDF for LLM agents or visual workflows:

bash
lit screenshot document.pdf -o ./screenshots

Next steps


Src Content Docs Liteparse Guides Agent Skill (`docs/src/content/docs/liteparse/guides/agent-skill.md`)


title: Agent Skill sidebar: order: 11

LiteParse ships as a coding agent skill — a self-contained capability that Claude Code, Cursor,
Codex, and other compatible agents load on demand. Installing it gives your agent the ability to
parse documents, extract text and bounding boxes, and generate screenshots, all locally and with no
API key.

Installation

Add the LiteParse skill to your project with the skills
CLI:

bash
npx skills add run-llama/llamaparse-agent-skills --skill liteparse

This downloads a skill file that compatible coding agents pick up automatically.

You can also copy SKILL.md
into your own skills setup by hand, or install the liteparse plugin from the
agent plugins marketplace to enable it from
inside Claude Code or Codex.

Requirements

Requirement Needed for
Node 18+ and npm i @llamaindex/liteparse The lit CLI the skill drives — verify with lit --version
LibreOffice Parsing Office files (DOCX, XLSX, PPTX)
uv The bundled ranked-search helper

No API key is required since everything runs on your machine.

What the skill teaches

The skill is more than an install: it encodes the extraction patterns that keep an agent's context
small and its runs cheap. Without it, agents commonly re-parse the same document on every search and
dump entire pages into the conversation.

  • Parse once to a file, then search that file. Each lit parse re-extracts the whole document,
    so the skill has the agent parse to a temp file and run all subsequent searches against it.
  • Minimize round-trips. Fetch a match and its surrounding context in a single command, and batch
    independent lookups together rather than spending a turn per search term.
  • Bound every output. Results are capped so a single lookup can't flood the context window.
  • Escalate to ranked search when keyword matching stalls, instead of firing off keyword variants
    one turn at a time.

Example prompts

Once the skill is installed, you can ask your coding agent things like:

  • "Parse this PDF and extract the text as JSON"
  • "Extract text from all the DOCX files in the ./contracts folder"
  • "Screenshot pages 1-5 of this PDF at 300 DPI"
  • "Parse this scanned document using the PaddleOCR server on localhost:8828"
  • "Get the bounding boxes for all text on page 3"

LiteParse is one of several ways to give an agent document-processing capabilities. For cloud parsing
of complex documents, MCP servers, and workflow nodes, see
Using LlamaIndex with AI Agents.


Src Content Docs Liteparse Guides Browser Usage (`docs/src/content/docs/liteparse/guides/browser-usage.md`)


title: Browser Usage (WASM) sidebar: order: 9

LiteParse ships a WebAssembly package that runs entirely in the browser — no server, no cloud calls. It supports PDF parsing and custom OCR engines implemented in JavaScript.

Install

bash
npm install @llamaindex/liteparse-wasm

Quick start

typescript
import init, { LiteParse } from "@llamaindex/liteparse-wasm";

// Load the WASM module
await init();

const parser = new LiteParse({
  ocrEnabled: false,
  outputFormat: "json",
});

// data is a Uint8Array (e.g. from <input type="file"> or fetch)
const bytes = new Uint8Array(await file.arrayBuffer());
const result = await parser.parse(bytes);

console.log(result.text);
console.log(result.pages[0]);

What works in the browser

  • PDF parsing from Uint8Array input (use file.arrayBuffer() to get bytes from a file picker for example)
  • Custom OCR via the ocrEngine callback interface (see below)
  • Text, JSON, and markdown output formats
  • Document complexity via parser.isComplex(bytes) — see the complexity guide
  • The extraction options — annotations, form fields, structure trees, vector graphics, and the rest. See Extraction options

What doesn't work

  • File path input — pass Uint8Array instead
  • DOCX/XLSX/PPTX conversion — requires LibreOffice, which isn't available in the browser
  • Built-in Tesseract or HTTP OCR — use the custom ocrEngine interface instead
  • Screenshots — not available in the WASM build
  • imageOutputDir — there is no filesystem to write to. Use extractImages and read the bytes from the result instead

OCR in the browser

The native Tesseract and HTTP OCR backends are not available in WASM. To use OCR, pass a custom ocrEngine object with a recognize method:

typescript
const parser = new LiteParse({
  ocrEnabled: true,
  ocrLanguage: "eng",
  // Use more than 1 only if the engine supports concurrent jobs.
  numWorkers: 2,
  ocrEngine: {
    /**
     * @param imageData PNG-encoded image bytes
     * @param width  rendered page width in pixels
     * @param height rendered page height in pixels
     * @param language e.g. "eng"
     * @returns array of { text, bbox: [x1, y1, x2, y2], confidence }
     */
    async recognize(imageData, width, height, language) {
      // e.g. call a Web Worker wrapping tesseract.js, or a remote OCR service
      return [
        { text: "Hello", bbox: [10, 20, 80, 40], confidence: 0.98 },
      ];
    },
  },
});

This lets you plug in any OCR implementation — a Web Worker running tesseract.js, a cloud OCR API, or anything else that returns text with bounding boxes.

LiteParse keeps at most numWorkers calls to recognize active. When one call
finishes, LiteParse prepares and starts the next page without waiting for the
other calls. An OCR engine can send the calls to a Web Worker pool or to
concurrent HTTP requests. Results stay assigned to their source pages when
jobs finish out of order. Active calls can keep up to numWorkers rendered
page rasters in memory, so use a small value for high-DPI documents.

PDF parsing and rendering remain single-threaded in WASM. numWorkers
defaults to 1 and limits concurrent OCR calls. LiteParse does not create
Web Workers. To process OCR jobs in parallel, supply an engine that supports
concurrent jobs.

Config options

All optional, camelCase:

Option Type Default Description
ocrLanguage string "eng" Language code passed to the OCR engine
ocrEnabled boolean false Run OCR on text-sparse pages. Off by default in WASM — there is no built-in engine, so this does nothing without ocrEngine
ocrEngine object — Custom JS-side OCR engine (see above)
numWorkers number 1 Maximum concurrent OCR calls
ocrFailureFatal boolean true When false, OCR failures return partial results instead of throwing
ocrHedgeDelaysMs number[] [] Request-hedging schedule for a remote ocrEngine
maxPages number 1000 Stop after this many pages
targetPages string — e.g. "1-5,10,15-20"
dpi number 150 Render DPI for OCR
outputFormat "json" | "text" | "markdown" "json" Shape of result.text. Also accepts "md". Throws on any other value
preserveVerySmallText boolean false Keep tiny text that's normally filtered
skipDiagonalText boolean false Drop text more than 2° off the nearest right angle
cropBox { top, right, bottom, left } — Fraction to crop from each side of every page
pageOrientationCorrections { page, angle }[] — Counter-rotate listed pages (1-based) by the clockwise angle (0/90/180/270) their content appears rotated
password string — Password for protected PDFs
quiet boolean false Suppress progress logging
imageMode "off" | "placeholder" | "embed" "placeholder" How image references appear in markdown. Also accepts "none" for off
extractLinks boolean true Render [text](url) in markdown
keepHeadersFooters boolean false Keep running header/footer chrome in markdown
emitWordBoxes boolean false Per-word sub-boxes on each text item

The extraction options — extractImages, extractVectorGraphics, extractAnnotations, extractFormFields, extractStructureTree, extractContentBounds, extractXfaPackets, extractTextMetadata, includeComplexity, and renderFormFields — are all available here too, with the same camelCase names and the same false defaults.


Src Content Docs Liteparse Guides Complexity (`docs/src/content/docs/liteparse/guides/complexity.mdx`)


title: Document Complexity sidebar: order: 4

The is_complex command in LiteParse checks whether a document is "complex". This means looking for images, broken/garbled text, large amounts of vector graphics, and sparse text.

Use it to:

  • Route documents to cheaper or more expensive parsing/OCR backends.
  • Reject or flag documents you can't handle — e.g. when running with --no-ocr, find the pages that would come back empty.
  • Estimate cost up front by counting how many pages actually need OCR.

How it works

Complexity is computed per page. Each page gets a needs_ocr verdict and a list of reasons explaining why it was flagged. A document is "complex" if any of its pages needs OCR.

The reasons are derived from a few signals:

Reason Meaning
scanned A single raster covers ~the whole page with little or no text behind it — a scanned/photographed page.
no-text Almost no extractable text and no full-page image — a blank page, cover, or divider.
sparse-text Some real text, but it covers very little of the page — typically a figure with a thin caption.
embedded-images Substantial embedded raster figures sit alongside the native text.
garbled The native text decodes to garbage and text is likely unreadable.
vector-text Text is painted as filled vector outlines outside the text layer, so no native text items represent it.
annotation-text Meaningful text lives in PDF annotations rather than the page content stream.

The set of reasons may grow over time as the router learns to recommend heavier pipelines. Treat reasons as an open-ended list and route on the values you care about rather than assuming it's exhaustive.

Layout signals

Alongside the OCR verdict, each page carries a nested layout object describing how hard the page is to lay out — multi-column bodies, tables, and dense figures. These are computed after spatial projection, so they see the reconstructed region tree rather than raw text items.

Layout complexity is deliberately separate from needs_ocr. A crisp, fully-digital two-column paper needs no OCR at all but is layout-complex; layout reasons never make needs_ocr true. Route on whichever you actually care about.

Reason Meaning
multi-column The page splits into more than one text column.
table-likely Ruled or borderless table structure was detected on the page.
dense-graphics Figures cover a large fraction of the page.
Field Description
column_count Number of detected text columns. Always ≥ 1.
ruled_table_count Number of tables detected from ruling lines.
ruled_table_coverage Fraction of the page covered by ruled tables (0–1).
text_table_run_count Number of borderless, text-aligned table runs detected.
figure_count Number of detected figure regions.
figure_coverage Fraction of the page covered by figures (0–1).
is_complex Verdict for layout specifically. Equivalent to reasons being non-empty.
reasons Every layout reason the page was flagged with (see above).

CLI

bash
lit is-complex document.pdf

The command always prints per-page JSON to stdout, a human-readable verdict to stderr, and sets its exit code to reflect the result — so you can consume it however fits your workflow.

jsonc
// stdout
[
  {
    "page_number": 1,
    "text_length": 0,
    "text_coverage": 0.0,
    "has_substantial_images": false,
    "image_block_count": 0,
    "image_coverage": 0.0,
    "largest_image_coverage": 0.0,
    "full_page_image": true,
    "uncovered_vector_area": null,
    "is_garbled": false,
    "page_area": 482400.0,
    "needs_ocr": true,
    "reasons": ["scanned"],
    "layout": {
      "column_count": 2,
      "ruled_table_count": 1,
      "ruled_table_coverage": 0.18,
      "text_table_run_count": 0,
      "figure_count": 3,
      "figure_coverage": 0.31,
      "is_complex": true,
      "reasons": ["multi-column", "table-likely", "dense-graphics"]
    }
  }
]
text
# stderr
COMPLEX — 1/1 page(s) need OCR

The exit code is non-zero when any page needs OCR, so the command works as a shell predicate:

bash
# Only parse with --no-ocr when the document is simple
lit is-complex document.pdf --quiet && lit parse document.pdf --no-ocr

Pipe the JSON into jq to act on individual pages. The example below will list the page numbers that need OCR:

bash
lit is-complex document.pdf --compact | jq '[.[] | select(.needs_ocr) | .page_number]'

Options

Flag Description
--compact Emit dense, whitespace-free JSON instead of pretty-printed.
--max-pages <N> Maximum number of pages to check (default: 1000).
--target-pages <SPEC> Check only specific pages, e.g. "1-5,10,15-20".
--password Password for encrypted/protected documents.
-q, --quiet Suppress the stderr logging output.

Inline with a parse

lit is-complex is a standalone pre-parse pass. If you're parsing the document anyway and just want the signals alongside the content, pass --complexity to lit parse instead — it adds a complexity object to each page of the JSON output, with the same fields described here.

bash
lit parse document.pdf --format json --complexity

This is off by default: computing layout signals requires the full spatial projection, so it only runs when you ask for it.

lit is-complex always populates layout. Within lit parse, the complexity object (and its layout) appears only when --complexity is set.

Library

The same check is available programmatically. It returns one entry per page.

typescript
import { LiteParse } from "@llamaindex/liteparse";

const parser = new LiteParse({ ocrEnabled: false });
const pages = await parser.isComplex("document.pdf");

const complex = pages.some((p) => p.needsOcr);
if (complex) {
  // Route to a heavyweight pipeline, like LlamaParse
  ...
} else {
  // Cheap path — skip OCR entirely
  const result = await parser.parse("document.pdf");
}

// Inspect why specific pages were flagged
for (const page of pages.filter((p) => p.needsOcr)) {
  console.log(`Page ${page.pageNumber}: ${page.reasons.join(", ")}`);
}
python
from liteparse import LiteParse

parser = LiteParse(ocr_enabled=False)
pages = parser.is_complex("document.pdf")

if any(p.needs_ocr for p in pages):
    # Route to a heavyweight pipeline, like LlamaParse
    ...
else:
    # Cheap path — skip OCR entirely
    result = parser.parse("document.pdf")

# Inspect why specific pages were flagged
for page in pages:
    if page.needs_ocr:
        print(f"Page {page.page_number}: {', '.join(page.reasons)}")
javascript
import { LiteParse } from "@llamaindex/liteparse-wasm";

const parser = new LiteParse();
const pages = await parser.isComplex(pdfBytes); // Uint8Array

const complex = pages.some((p) => p.needsOcr);

Both the library and CLI accept raw bytes as well as file paths, so you can run the check on documents you've already loaded into memory.

Per-page fields

Every entry includes the raw signals behind the verdict, so you can apply your own thresholds instead of relying solely on needs_ocr:

Field Description
page_number 1-indexed page number.
needs_ocr Verdict: the page needs more than the cheap text-only path. Equivalent to reasons being non-empty.
reasons Every reason the page was flagged (see the table above). Empty exactly when needs_ocr is false.
text_length Length of usable native text (garbled/unmappable text excluded).
text_coverage Fraction of the page area covered by native text (0–1).
has_substantial_images Whether any counted inline raster figures are present.
image_block_count Number of counted raster image objects (full-page backgrounds excluded).
image_coverage Summed image-bbox area over page area, clamped to 1.
largest_image_coverage Largest single counted image's area over page area, clamped to 1.
full_page_image A single raster covers ≥90% of the page — the signal that tells a scan apart from a blank page.
uncovered_vector_area Filled vector-outline area not covered by native text (pt²). null/undefined when a cheaper signal already decided the page.
is_garbled Whether the native text decodes to garbage.
page_area Page area in pt².
layout Nested layout signals for the page. Omitted when layout was not computed.

Next steps


Src Content Docs Liteparse Guides Extraction (`docs/src/content/docs/liteparse/guides/extraction.mdx`)


title: Extraction Options sidebar: order: 3

By default, LiteParse returns text, geometry, and page metadata. Everything a PDF can carry beyond that (embedded images, vector art, annotations, form fields, tagged-structure trees, per-glyph typography) is opt-in, behind its own flag.

This keeps the default output shape small, stable, and cheap to produce. It also means that if you're looking for data you know is in the file and don't see it in the output, you're probably missing a flag.

Each option costs parse time and output size. Turn on only what you consume.

Quick reference

Rust / Python / CLI Node / WASM Adds
extract_images / --extract-images extractImages images[], image_error_count
extract_vector_graphics / --extract-vector-graphics extractVectorGraphics pages[].vector_graphics
extract_annotations / --extract-annotations extractAnnotations pages[].annotations
extract_form_fields / --extract-form-fields extractFormFields pages[].form_fields
extract_structure_tree / --extract-structure-tree extractStructureTree pages[].structure_tree
extract_blocks / --extract-blocks extractBlocks pages[].blocks
extract_content_bounds / --extract-content-bounds extractContentBounds pages[].content_bounds
extract_xfa_packets / --extract-xfa-packets extractXfaPackets xfa_packets[]
extract_text_metadata / --extract-text-metadata extractTextMetadata extra keys on text_items[]
include_complexity / --complexity includeComplexity pages[].complexity
emit_word_boxes (no CLI flag) emitWordBoxes TextItem.words (bindings only)

One toggle is on by default: extract_links (--no-links to disable). See the Markdown guide.

Enabling options

typescript
import { LiteParse } from "@llamaindex/liteparse";

const parser = new LiteParse({
  outputFormat: "json",
  extractImages: true,
  extractFormFields: true,
  extractAnnotations: true,
  extractTextMetadata: true,
});

const result = await parser.parse("form.pdf");
python
from liteparse import LiteParse

parser = LiteParse(
    output_format="json",
    extract_images=True,
    extract_form_fields=True,
    extract_annotations=True,
    extract_text_metadata=True,
)

result = parser.parse("form.pdf")
rust
use liteparse::{LiteParse, LiteParseConfig, OutputFormat};

let config = LiteParseConfig {
    output_format: OutputFormat::Json,
    extract_images: true,
    extract_form_fields: true,
    extract_annotations: true,
    extract_text_metadata: true,
    ..Default::default()
};

let result = LiteParse::new(config).parse("form.pdf").await?;
bash
lit parse form.pdf --format json \
  --extract-images \
  --extract-form-fields \
  --extract-annotations \
  --extract-text-metadata

Embedded images

extract_images decodes embedded raster images and returns their bytes and metadata in a document-level images array. Pair it with image_output_dir to write each image to disk instead of holding it in memory.

bash
lit parse report.pdf --format json --extract-images --image-output-dir ./images
json
{
  "images": [
    {
      "id": "p1_i0",
      "name": "image_p1_0.png",
      "path": "./images/image_p1_0.png",
      "page": 1,
      "bbox": { "x": 72.0, "y": 118.5, "width": 240.0, "height": 160.0 },
      "width": 800,
      "height": 533,
      "rotation": 0.0,
      "format": "png"
    }
  ],
  "image_error_count": 0
}

Notes:

  • image_output_dir requires extract_images — setting it alone is a config error. image_mode: "embed" also implies extraction, for backwards compatibility.
  • duplicate_of is set when the same image appears more than once in the document; the duplicate points at the canonical entry's id rather than re-storing the pixels.
  • image_error_count counts images that failed to decode. It is omitted when zero.
  • This is separate from image_mode, which only controls how image references are written into markdown. See the Markdown guide.

Vector graphics

extract_vector_graphics reports the vector drawing operations on each page, split into filled/stroked shapes and straight lines. Useful for finding table rules, underlines, dividers, and chart geometry.

json
{
  "vector_graphics": {
    "shapes": [
      {
        "bbox": { "x": 72.0, "y": 400.0, "width": 180.0, "height": 90.0 },
        "stroke": true,
        "stroke_color": "#000000",
        "fill": false,
        "has_curve": true
      }
    ],
    "lines": [
      {
        "x1": 72.0, "y1": 512.0, "x2": 540.0, "y2": 512.0,
        "stroke": true,
        "stroke_width": 0.5,
        "stroke_color": "#cccccc",
        "fill": false
      }
    ]
  }
}

Annotations

extract_annotations returns PDF annotations per page — comments, highlights, and link targets.

json
{
  "annotations": [
    {
      "subtype": "Link",
      "rect": { "x": 72.0, "y": 300.0, "width": 120.0, "height": 12.0 },
      "uri": "https://example.com"
    },
    {
      "subtype": "Highlight",
      "contents": "check this figure",
      "title": "Reviewer 2",
      "created": "D:20260714093000Z",
      "quadpoint_rects": [
        { "x": 72.0, "y": 280.0, "width": 200.0, "height": 11.0 }
      ]
    }
  ]
}

Only subtype is always present; every other key is omitted when the annotation doesn't carry it.

Form fields

extract_form_fields returns AcroForm widgets and their resolved values.

json
{
  "form_fields": [
    {
      "id": "f0",
      "type": "text",
      "page": 1,
      "annotation_index": 0,
      "widget_index": 0,
      "field_flags": 0,
      "name": "applicant_name",
      "value": "Ada Lovelace",
      "rect": { "x": 150.0, "y": 640.0, "width": 220.0, "height": 18.0 }
    },
    {
      "id": "f1",
      "type": "checkbox",
      "page": 1,
      "annotation_index": 1,
      "widget_index": 0,
      "field_flags": 0,
      "name": "agree_terms",
      "checked": true
    }
  ]
}

The widget type is serialized as type in JSON, and is type on the Node and Python objects. Radio groups and checkbox sets expose control_count/control_index to tie sibling widgets together, and choice fields expose options/selected_options.

For XFA-based forms (a different, XML-based form technology), use extract_xfa_packets instead, which returns the raw XFA packets at document level.

Related: render_form_fields draws filled-in field appearances into rendered rasters. It affects screenshots and OCR input pixels, not JSON keys.

Structure tree

extract_structure_tree returns the tagged-PDF logical structure — the authoring-time document outline of paragraphs, headings, tables, and lists. Only present in PDFs that were actually tagged.

json
{
  "structure_tree": {
    "roots": [
      {
        "type": "Document",
        "children": [
          {
            "type": "H1",
            "marked_content_ids": [0],
            "actual_text": "Quarterly Report",
            "children": []
          },
          {
            "type": "P",
            "marked_content_ids": [1, 2],
            "children": []
          }
        ]
      }
    ]
  }
}

marked_content_ids join back to the mcid field on text items (available with extract_text_metadata), letting you map reconstructed text to its authored role.

Layout blocks

extract_blocks returns the page decomposed into blocks — headings, paragraphs, list items, tables, code, rules, and figures — each with the region of the page it came from. This is the same classification the Markdown renderer runs on.

bash
lit parse document.pdf --format json --extract-blocks

Only the fields that apply to a block's kind are present:

json
{
  "kind": "heading",
  "text": "Quarterly Report",
  "level": 1,
  "bbox": { "x": 72.0, "y": 114.88, "width": 284.0, "height": 21.0 }
}
Field Kinds Meaning
kind all heading, paragraph, list_item, code, table, grid_fallback, rule, figure
bbox all Region occupied, in the same coordinate space as text_items
text heading, paragraph, list_item Rendered text
level heading, list_item Heading level 1–6, or list nesting depth
bold, italic paragraph, list_item Set when the whole block is emphasized
ordered, marker list_item Whether the list is ordered, and the marker as it appeared (138., iii), •)
lines, lang code, grid_fallback Verbatim source lines, and a best-effort language hint
header, rows table Cells, each with its own text and bbox
id, format figure Matches the img_{id}.{format} target in Markdown output

bbox is the union of every source line that fed the block, so a wrapped heading or multi-line paragraph reports the whole band it occupies — not just its first line.

Tables

Table cells are objects, not bare strings: each carries its own bbox, so a cell maps back to the region it was read from.

json
{
  "kind": "table",
  "bbox": { "x": 67.9, "y": 511.1, "width": 458.4, "height": 111.0 },
  "header": [
    { "text": "Title of each class", "bbox": { "x": 132.7, "y": 511.1, "width": 64.0, "height": 8.0 } },
    { "text": "Trading symbol(s)",   "bbox": { "x": 288.7, "y": 511.1, "width": 35.0, "height": 8.0 } }
  ],
  "rows": [
    [
      { "text": "Common Stock", "bbox": { "x": 67.9, "y": 522.4, "width": 193.0, "height": 9.5 } },
      { "text": "AAPL",         "bbox": { "x": 293.8, "y": 522.4, "width": 24.0, "height": 9.5 } }
    ]
  ]
}

For a ruled table, a cell's box is the drawn grid cell. For a borderless one, it is the extent of the spans the cell was built from. Cells that exist only to square off a ragged grid have no bbox — there is no ink behind them.

Getting a table into a dataframe is then a two-line transform:

python
import pandas as pd
from liteparse import LiteParse

result = LiteParse(extract_blocks=True).parse("10-k.pdf")

for page in result.pages:
    for block in page.blocks or []:
        if block.kind == "table":
            df = pd.DataFrame(
                [[c.text for c in row] for row in block.rows],
                columns=[c.text for c in block.header or []],
            )
typescript
import { LiteParse } from "@llamaindex/liteparse";

const { pages } = await new LiteParse({ extractBlocks: true }).parse("10-k.pdf");

for (const page of pages) {
  for (const block of page.blocks ?? []) {
    if (block.kind !== "table") continue;
    const grid = [block.header ?? [], ...block.rows].map((r) => r.map((c) => c.text));
  }
}

Text metadata

extract_text_metadata enriches every entry in text_items[] with typography detail. Without it, a text item carries only text, x, y, width, height, font_name, and font_size.

json
{
  "text": "Quarterly Report",
  "x": 72.0, "y": 118.5, "width": 180.4, "height": 14.0,
  "rotation": 0.0,
  "font_name": "Helvetica-Bold",
  "font_size": 14.0,
  "font_height": 14.2,
  "font_ascent": 11.1,
  "font_descent": -3.1,
  "font_weight": 700,
  "text_width": 180.4,
  "font_is_buggy": false,
  "mcid": 0,
  "fill_color": "#111111"
}

font_weight and fill_color are the practical way to detect emphasis and headings yourself when you need more control than markdown output gives you. font_is_buggy flags fonts whose metrics LiteParse had to estimate — treat sizes from those items with suspicion.

Content bounds

extract_content_bounds adds a content_bounds rect per page: the bounding box of actual content, ignoring the page's declared media box. Use it to crop whitespace or detect pages whose content sits far from the nominal page area.

json
{ "content_bounds": { "x": 68.0, "y": 96.0, "width": 476.0, "height": 620.0 } }

Word boxes

emit_word_boxes splits each text item into per-word sub-boxes (TextItem.words), for word-level bounding-box attribution.

This one is library- and bindings-only. Word boxes are deliberately excluded from --format json, because on a text-heavy document they dwarf the rest of the payload. There is no CLI flag; reach for it from Node, Python, Rust, or WASM.

typescript
const parser = new LiteParse({ emitWordBoxes: true });
const result = await parser.parse("report.pdf");

for (const item of result.pages[0].textItems) {
  for (const word of item.words ?? []) {
    console.log(word.text, word.x, word.y, word.width, word.height);
  }
}
python
parser = LiteParse(emit_word_boxes=True)
result = parser.parse("report.pdf")

for item in result.pages[0].text_items:
    for word in item.words:
        print(word.text, word.x, word.y, word.width, word.height)

Options with no CLI flag

A few config options are only reachable from the library and bindings:

Option Description
emit_word_boxes Per-word sub-boxes (see above).
crop_box Restrict output to a sub-region of each page. A text item is kept only if it falls entirely inside.
skip_diagonal_text Drop text rotated more than 2° off the nearest right angle — watermarks and diagonal stamps.
page_orientation_corrections Counter-rotate specific pages before extraction, using angles from your own orientation classifier (see below).
detect_screenshot_rects Populate rects on screenshot results.
render_form_fields Draw form-field appearances into rendered pages.
ocr_failure_fatal When false, a systemic OCR failure returns partial results instead of erroring. Defaults to true.
ocr_hedge_delays_ms Request-hedging schedule for HTTP OCR servers. No effect on built-in Tesseract.

crop_box takes the fraction to crop from each side. It is an object in Node and WASM, and a (top, right, bottom, left) tuple in Python:

typescript
// Drop the top 10% and bottom 5% of every page
const parser = new LiteParse({
  cropBox: { top: 0.1, right: 0.0, bottom: 0.05, left: 0.0 },
});
python
# (top, right, bottom, left)
parser = LiteParse(crop_box=(0.1, 0.0, 0.05, 0.0))

Correcting page orientation

LiteParse honours a page's /Rotate entry, but a scanned page whose content is
upside down or sideways carries no such hint: its text layer (or OCR raster) reads
bottom-up, so reading order and label/value pairs come out inverted. If you run an
orientation classifier on the rendered page, hand its verdict to
page_orientation_corrections. Each entry names a 1-based page and the clockwise
angle (0, 90, 180 or 270) by which that page's content appears rotated;
LiteParse counter-rotates the page before extraction, so text coordinates, reading
order, page dimensions, OCR rasters and screenshots all come out upright. Pages not
listed, or past the end of the document, are left unchanged.

typescript
// Page 3 was scanned upside down, page 7 sideways (content rotated 90° clockwise)
const parser = new LiteParse({
  pageOrientationCorrections: [
    { page: 3, angle: 180 },
    { page: 7, angle: 90 },
  ],
});
python
# (page, angle) pairs
parser = LiteParse(page_orientation_corrections=[(3, 180), (7, 90)])

Next steps


Src Content Docs Liteparse Guides Library Usage (`docs/src/content/docs/liteparse/guides/library-usage.mdx`)


title: Library Usage sidebar: order: 1

LiteParse can be used as a library in your own code, not just from the CLI. There are native packages for both TypeScript and Python.

TypeScript / Node.js usage

Install as a project dependency:

bash
npm install @llamaindex/liteparse
# or
pnpm add @llamaindex/liteparse

Parsing a document

typescript
import { LiteParse } from "@llamaindex/liteparse";

const parser = new LiteParse({ ocrEnabled: true });
const result = await parser.parse("document.pdf");

// Full document text with layout preserved
console.log(result.text);

// Per-page data
for (const page of result.pages) {
  console.log(`Page ${page.pageNum}: ${page.textItems.length} text items`);
}

JSON output with bounding boxes

Text items include spatial coordinates (x, y, width, height) in PDF points:

typescript
const parser = new LiteParse({ outputFormat: "json" });
const result = await parser.parse("document.pdf");

for (const page of result.pages) {
  for (const item of page.textItems) {
    console.log(`[${item.x}, ${item.y}] ${item.width}×${item.height} "${item.text}"`);
  }
}

Markdown output

Render the document to Markdown — headings, tables, lists, images, and links. The rendered Markdown comes back on result.text:

typescript
const parser = new LiteParse({
  outputFormat: "markdown",   // "json" | "text" | "markdown"
  imageMode: "placeholder",   // "placeholder" | "off" | "embed"
  extractLinks: true,         // render [text](url) link syntax (default: true)
});
const result = await parser.parse("document.pdf");
console.log(result.text); // rendered Markdown

Configuration

Pass any config options to the constructor. You only need to specify what you want to override:

typescript
const parser = new LiteParse({
  ocrEnabled: true,
  ocrServerUrl: "http://localhost:8828/ocr",
  ocrLanguage: "fra",
  dpi: 300,
  outputFormat: "json",
  targetPages: "1-10",
  password: "secret",
});

Images, vector graphics, annotations, form fields, structure trees, and per-item typography are all off by default and enabled with their own options. See Extraction options.

Buffer / Uint8Array input

You can pass raw bytes directly instead of a file path:

typescript
import { readFile } from "fs/promises";

const parser = new LiteParse();

// From a file read
const pdfBytes = await readFile("document.pdf");
const result = await parser.parse(pdfBytes);

// From an HTTP response
const response = await fetch("https://example.com/document.pdf");
const buffer = Buffer.from(await response.arrayBuffer());
const result2 = await parser.parse(buffer);

Screenshots

Generate page images as buffers — useful for sending to LLMs or saving to disk:

typescript
const parser = new LiteParse();
const screenshots = await parser.screenshot("document.pdf");

for (const shot of screenshots) {
  console.log(`Page ${shot.pageNum}: ${shot.width}x${shot.height}`);
  // shot.imageBuffer contains the raw PNG data
}

// Screenshot specific pages
const shots = await parser.screenshot("document.pdf", [1, 2, 3]);

Environment variables

Variable Description
TESSDATA_PREFIX Path to a directory containing Tesseract .traineddata files. For offline/air-gapped environments. Also available as the tessdataPath config option.

Python usage

Install the package from PyPI:

bash
pip install liteparse

Parsing a document

python
from liteparse import LiteParse

parser = LiteParse()
result = parser.parse("document.pdf")

# Full document text
print(result.text)

# Per-page data
for page in result.pages:
    print(f"Page {page.page_num}: {len(page.text_items)} text items")

Configuration

All options are passed to the constructor:

python
parser = LiteParse(
    ocr_enabled=True,
    ocr_server_url="http://localhost:8828/ocr",
    ocr_language="fra",
    dpi=300,
    target_pages="1-5",
    password="secret",
)
result = parser.parse("document.pdf")

All options are keyword-only. Images, vector graphics, annotations, form fields, structure trees, and per-item typography are off by default and enabled with their own options — see Extraction options.

Markdown output

Render the document to Markdown — headings, tables, lists, images, and links. The rendered Markdown comes back on result.text:

python
parser = LiteParse(
    output_format="markdown",   # "json" | "text" | "markdown"
    image_mode="placeholder",   # "placeholder" | "off" | "embed"
    extract_links=True,         # render [text](url) link syntax (default: True)
)
result = parser.parse("document.pdf")
print(result.text)  # rendered Markdown

Parsing from bytes

If you already have file contents in memory (e.g. from a web upload), pass them directly to parse():

python
with open("document.pdf", "rb") as f:
    pdf_bytes = f.read()

result = parser.parse(pdf_bytes)
print(result.text)

Screenshots

python
screenshots = parser.screenshot("document.pdf", page_numbers=[1, 2, 3])
for s in screenshots:
    print(f"Page {s.page_num}: {s.width}x{s.height}")
    # s.image_bytes contains PNG data

Environment variables

Variable Description
TESSDATA_PREFIX Path to a directory containing Tesseract .traineddata files. For offline/air-gapped environments. Also available as the tessdata_path constructor option.

Rust usage

Add liteparse to your Cargo.toml:

toml
[dependencies]
liteparse = "2"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Parsing a document

rust
use liteparse::{LiteParse, LiteParseConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let parser = LiteParse::new(LiteParseConfig::default());
    let result = parser.parse("document.pdf").await?;

    // Full document text
    println!("{}", result.text);

    // Per-page data
    for page in &result.pages {
        println!("Page {}: {} text items", page.page_number, page.text_items.len());
    }
    Ok(())
}

Parsing from bytes

Use PdfInput::Bytes when you have the PDF in memory:

rust
use liteparse::{LiteParse, LiteParseConfig};
use liteparse::types::PdfInput;

let parser = LiteParse::new(LiteParseConfig::default());
let pdf_bytes = std::fs::read("document.pdf")?;
let result = parser.parse_input(PdfInput::Bytes(pdf_bytes)).await?;

Configuration

Override only the fields you need:

rust
use liteparse::{LiteParse, LiteParseConfig, OutputFormat};

let config = LiteParseConfig {
    ocr_enabled: true,
    ocr_language: "fra".to_string(),
    dpi: 300.0,
    target_pages: Some("1-10".to_string()),
    output_format: OutputFormat::Json,
    password: Some("secret".to_string()),
    ..Default::default()
};
let parser = LiteParse::new(config);

LiteParseConfig is a plain struct, so ..Default::default() is the idiomatic way to set only what you need. Images, vector graphics, annotations, form fields, structure trees, and per-item typography are off by default — see Extraction options.

Markdown output

Render the document to Markdown — headings, tables, lists, images, and links. The rendered Markdown comes back on result.text:

rust
use liteparse::config::{ImageMode, LiteParseConfig, OutputFormat};
use liteparse::LiteParse;

let config = LiteParseConfig {
    output_format: OutputFormat::Markdown,
    image_mode: ImageMode::Placeholder,
    extract_links: true,
    ..Default::default()
};
let result = LiteParse::new(config).parse("document.pdf").await?;
println!("{}", result.text); // rendered Markdown

Screenshots

Generate page images as PNG bytes:

rust
let parser = LiteParse::new(LiteParseConfig::default());
let screenshots = parser.screenshot("document.pdf", None).await?;

for shot in &screenshots {
    println!("Page {}: {}x{}", shot.page_num, shot.width, shot.height);
    // shot.image_bytes contains the raw PNG data
}

// Screenshot specific pages
let shots = parser.screenshot("document.pdf", Some(vec![1, 2, 3])).await?;

Custom OCR engine

Implement the OcrEngine trait to plug in your own OCR backend:

rust
use liteparse::LiteParse;
use liteparse::ocr::OcrEngine;
use std::sync::Arc;

let parser = LiteParse::new(Default::default())
    .with_ocr_engine(Arc::new(my_engine));

For native ONNX OCR, enable LiteParse's oar-ocr feature and pass local model
paths to OarOcrEngine::from_models. Enabling oar-ocr-auto-download also lets
the same constructor accept registered bare file names; oar-ocr downloads
them from ModelScope, verifies their SHA-256 digests, and caches them under
$OAR_HOME (default ~/.oar). The OAR backend is intentionally Rust-only and
is not included in the Node.js, Python, or WASM binding binaries.

Features

Feature Default Description
tesseract Yes Built-in Tesseract OCR via tesseract-rs. Disable with default-features = false if you only use HTTP OCR or no OCR.
oar-ocr No Native ONNX OCR via oar-ocr, using local or in-memory models. Available through the non-WASM Rust API.
oar-ocr-auto-download No Enables verified download and caching of registered OAR model file names.
oar-ocr-{cuda,tensorrt,directml,coreml,webgpu,openvino} No Forwards the selected ONNX Runtime execution provider to oar-ocr.

Browser (WASM) usage

Install the WASM package:

bash
npm install @llamaindex/liteparse-wasm

Parsing a document

typescript
import init, { LiteParse } from "@llamaindex/liteparse-wasm";

// Initialize the WASM module (required once before use)
await init();

const parser = new LiteParse({ ocrEnabled: false });

// Pass a Uint8Array (e.g. from fetch, File input, or drag-drop)
const bytes = new Uint8Array(await file.arrayBuffer());
const result = await parser.parse(bytes);

console.log(result.text);          // full document text
console.log(result.pages[0]);      // per-page items with bboxes

Configuration

All options are optional and use camelCase:

typescript
const parser = new LiteParse({
  ocrEnabled: true,
  ocrLanguage: "fra",
  dpi: 300,
  outputFormat: "json",
  targetPages: "1-10",
  maxPages: 100,
  password: "secret",
  quiet: false,
});

Markdown output

The browser build supports Markdown output too — set outputFormat: "markdown" and read the rendered Markdown from result.text. See the Markdown output guide for the imageMode and extractLinks options.

typescript
const parser = new LiteParse({ outputFormat: "markdown" });
const result = await parser.parse(bytes);
console.log(result.text); // rendered Markdown

OCR in the browser

The native Tesseract and HTTP OCR backends are not available in the browser. To use OCR, provide a JS-side engine with a recognize method:

typescript
const parser = new LiteParse({
  ocrEnabled: true,
  ocrLanguage: "eng",
  ocrEngine: {
    /**
     * @param imageData - PNG-encoded image bytes
     * @param width     - rendered page width in pixels
     * @param height    - rendered page height in pixels
     * @param language  - e.g. "eng"
     * @returns array of recognized text regions
     */
    async recognize(imageData, width, height, language) {
      // e.g. call tesseract.js in a worker, or a remote OCR service
      return [
        { text: "Hello", bbox: [10, 20, 80, 40], confidence: 0.98 },
      ];
    },
  },
});

Notes

  • Input must be a Uint8Array — file paths are not supported in the browser.
  • The WASM module must be initialized with await init() before creating a LiteParse instance.

Src Content Docs Liteparse Guides Markdown (`docs/src/content/docs/liteparse/guides/markdown.mdx`)


title: Markdown Output sidebar: order: 2

LiteParse can render documents directly to Markdown, reconstructing headings,
tables, lists, images, and links from the spatial layout. This is ideal for
feeding documents to LLMs and RAG pipelines, where clean, structured text
matters more than exact visual fidelity.

Markdown is a first-class output format alongside text and json.

CLI

bash
# Render a document to Markdown
lit parse document.pdf --format markdown -o output.md

# Print Markdown to stdout
lit parse document.pdf --format markdown

Images

By default, raster images are emitted as Markdown placeholders
(``) in reading order. Control this with --image-mode:

Mode Behavior
placeholder (default) Emit `` references in reading order
off Strip images entirely
embed Emit the same references, and extract the image bytes

--image-mode controls only how image references appear in the markdown. It does
not, on its own, decode any pixels. To actually get image bytes, use
--extract-images (see Extraction options):

bash
# Strip images
lit parse document.pdf --format markdown --image-mode off

# Extract embedded images to disk and reference them from the markdown
lit parse document.pdf --format markdown --extract-images --image-output-dir ./images

--image-output-dir requires --extract-images. For backwards compatibility
--image-mode embed also turns extraction on, so the older
--image-mode embed --image-output-dir ./images form still works.

Hyperlink annotations are rendered as [text](url) by default. Pass
--no-links to emit the anchor text as plain text instead:

bash
lit parse document.pdf --format markdown --no-links

Headers and footers

Running headers and footers (page numbers, document titles repeated on every
page) are stripped from markdown output by default, since they interrupt the
prose when chunked for retrieval. Pass --keep-headers-footers to retain them:

bash
lit parse document.pdf --format markdown --keep-headers-footers

Library

The rendered Markdown is returned on result.text.

typescript
import { LiteParse } from "@llamaindex/liteparse";

const parser = new LiteParse({
  outputFormat: "markdown",   // "json" | "text" | "markdown"
  imageMode: "placeholder",   // "placeholder" | "off" | "embed" (default: "placeholder")
  extractLinks: true,         // render [text](url) link syntax (default: true)
});
const result = await parser.parse("document.pdf");
console.log(result.text); // rendered Markdown
python
from liteparse import LiteParse

parser = LiteParse(
    output_format="markdown",   # "json" | "text" | "markdown"
    image_mode="placeholder",   # "placeholder" | "off" | "embed"
    extract_links=True,         # render [text](url) link syntax (default: True)
)
result = parser.parse("document.pdf")
print(result.text)  # rendered Markdown
rust
use liteparse::config::{ImageMode, LiteParseConfig, OutputFormat};
use liteparse::LiteParse;

let config = LiteParseConfig {
    output_format: OutputFormat::Markdown,
    image_mode: ImageMode::Placeholder,
    extract_links: true,
    ..Default::default()
};
let result = LiteParse::new(config).parse("document.pdf").await?;
println!("{}", result.text); // rendered Markdown

Quality notes

Markdown reconstruction quality varies with document complexity. LiteParse does
a strong job on typical documents, handling prose, headings, simple-to-moderate tables,
and lists. This runs entirely locally with no models using rule-based heuristics. For the hardest documents (dense or
multi-level tables, complex multi-column layouts, charts, and scans),
LlamaParse
remains the most accurate option.


Src Content Docs Liteparse Guides Multi Format (`docs/src/content/docs/liteparse/guides/multi-format.mdx`)


title: Multi-Format Support sidebar: order: 6

LiteParse automatically converts non-PDF formats to PDF before parsing. This lets you use the same parsing pipeline for Office documents, images, and more.

Supported formats

Office documents (via LibreOffice)

Category Extensions
Word .doc, .docx, .docm, .odt, .rtf, .pages
PowerPoint .ppt, .pptx, .pptm, .odp, .key
Spreadsheets .xls, .xlsx, .xlsm, .ods, .csv, .tsv, .numbers

Images (native support)

.jpg, .jpeg, .png, .gif, .bmp, .tiff, .webp, .svg

Images are converted to PDF and then parsed with OCR to extract text.

Installing dependencies

Format conversion uses standard system tools. Install the ones you need:

LibreOffice (for Office documents)

bash
# macOS
brew install --cask libreoffice

# Ubuntu/Debian
apt-get install libreoffice

# Windows
choco install libreoffice-fresh

On Windows, you may need to add the LibreOffice CLI directory (typically C:\Program Files\LibreOffice\program) to your PATH and restart.

Usage

Once the dependencies are installed, just pass any supported file to lit parse:

bash
lit parse report.docx
lit parse slides.pptx --format json
lit parse spreadsheet.xlsx -o output.txt
lit parse scan.png

Batch mode also handles mixed formats:

bash
lit batch-parse ./documents ./output --recursive

How it works

  1. LiteParse detects the file extension
  2. If it's not a PDF, it converts to PDF — office documents via LibreOffice, images and SVGs natively in Rust
  3. The resulting PDF is parsed normally
  4. Temporary conversion files are cleaned up automatically

Only office formats need an external tool (LibreOffice). If it isn't installed, LiteParse returns an error explaining which dependency is needed.


Src Content Docs Liteparse Guides Ocr (`docs/src/content/docs/liteparse/guides/ocr.md`)


title: OCR Configuration sidebar: order: 5

LiteParse uses OCR selectively — only on embedded images or pages where native text extraction didn't find text. This keeps parsing fast while still capturing text from scanned pages and embedded images.

Built-in Tesseract (default)

Tesseract is bundled with LiteParse and works out of the box. Just run:

bash
lit parse document.pdf

Language support

Specify the OCR language for better accuracy on non-English documents:

bash
lit parse document.pdf --ocr-language fra    # French
lit parse document.pdf --ocr-language deu    # German
lit parse document.pdf --ocr-language jpn    # Japanese

Tesseract uses ISO 639-3 language codes (eng, fra, deu, etc.).

Offline / air-gapped environments

For environments without internet access, point Tesseract at a local directory containing pre-downloaded .traineddata files:

bash
# Via environment variable
export TESSDATA_PREFIX=/path/to/tessdata
lit parse document.pdf --ocr-language eng

# Or via CLI flag
lit parse document.pdf --tessdata-path /path/to/tessdata

The tessdata_path / tessdataPath option is also available in the library APIs.

Troubleshooting: missing language data

If Tesseract can't find its language data, you'll see an error like:

text
Error opening data file tessdata/eng.traineddata
Please make sure the TESSDATA_PREFIX environment variable is set to your "tessdata" directory.
Failed loading language 'eng'

The bundled Tesseract still needs the .traineddata file for your language. It is normally downloaded and cached automatically on first use (under ~/.tesseract-rs/tessdata on Linux, ~/Library/Application Support/tesseract-rs/tessdata on macOS); if that download didn't happen — e.g. offline, restricted network, or a sandboxed install — OCR cannot run.

To resolve it, do any one of the following:

  • Download the language file (e.g. eng.traineddata) and point LiteParse at it with TESSDATA_PREFIX or --tessdata-path (see above).
  • Use an HTTP OCR server instead of the built-in engine.
  • Disable OCR with --no-ocr if you don't need it.

When language data is missing for every page, LiteParse now fails with a clear OCR failed for all N page(s) error instead of returning a document with silently-empty OCR text — so an unresolved setup surfaces immediately rather than producing partial results that look complete.

Disabling OCR

If you don't need OCR (pure native-text PDFs, or you don't care about images), disable it for faster parsing:

bash
lit parse document.pdf --no-ocr

HTTP OCR servers

For higher accuracy or GPU-accelerated OCR, you can point LiteParse at an HTTP OCR server. LiteParse ships with ready-to-use examples for popular OCR engines.

EasyOCR

bash
# Start the EasyOCR server (requires Python)
git clone https://github.com/run-llama/liteparse.git
cd liteparse/ocr/easyocr
pip install -r requirements.txt
python server.py

# Parse with EasyOCR in another terminal
lit parse document.pdf --ocr-server-url http://localhost:8828/ocr

PaddleOCR

bash
# Start the PaddleOCR server (requires Python)
git clone https://github.com/run-llama/liteparse.git
cd liteparse/ocr/paddleocr
pip install -r requirements.txt
python server.py

# Parse with PaddleOCR in another terminal
lit parse document.pdf --ocr-server-url http://localhost:8828/ocr

Authenticated servers

Pass extra HTTP headers on every OCR request with --ocr-server-header, repeating the flag for each header:

bash
lit parse document.pdf \
  --ocr-server-url https://ocr.internal/ocr \
  --ocr-server-header "Authorization: Bearer $TOKEN" \
  --ocr-server-header "X-Tenant: acme"

In the libraries this is ocr_server_headers / ocrServerHeaders, taking a map of header name to value. It has no effect unless an OCR server URL is set.

Parallel OCR workers

LiteParse OCRs multiple pages in parallel. By default, it uses one fewer worker than your CPU core count. Override this with:

bash
lit parse document.pdf --num-workers 8

This is useful if you need to slow down OCR requests to an external server or if your OCR engine is GPU-accelerated and can handle more concurrency.

Custom OCR servers

You can integrate any OCR engine by implementing the LiteParse OCR API. Your server needs a single endpoint:

text
POST /ocr
Content-Type: multipart/form-data

Request fields:

Field Type Required Description
file binary Yes Image file (PNG, JPG, etc.)
language string No ISO 639-1 language code (default: en)

Watch the language code. LiteParse forwards whatever --ocr-language is set to, which defaults to the Tesseract-style ISO 639-3 code eng — not en. Your server should accept both. The bundled EasyOCR and PaddleOCR servers do this by mapping eng → en before dispatch; do the same in a custom server, or pass --ocr-language en explicitly.

Response format:

json
{
  "results": [
    {
      "text": "recognized text",
      "bbox": [12, 40, 220, 62],
      "confidence": 0.95
    }
  ]
}

Each result contains:

Field Type Description
text string Recognized text
bbox [x1, y1, x2, y2] Bounding box in pixels. Origin is top-left, x goes right, y goes down
confidence number Score from 0.0 to 1.0

Testing your server

bash
# Quick test with curl
curl -X POST http://localhost:8828/ocr \
  -F "[email protected]" \
  -F "language=en" | jq .

# Use with LiteParse
lit parse document.pdf --ocr-server-url http://localhost:8828/ocr

Common Gotchas

  • Return {"results": []} if no text is detected
  • Bounding boxes must be axis-aligned ([x1, y1, x2, y2] where top-left to bottom-right)
  • If your engine returns rotated boxes, convert to axis-aligned by taking min/max coordinates
  • If your engine doesn't provide confidence scores, return 1.0
  • Results should be in reading order (top-to-bottom, left-to-right)
  • Cache OCR models in memory rather than reloading per request

OCR in the browser (WASM)

The built-in Tesseract and HTTP OCR backends are not available in the WASM build. Instead, you can pass a custom ocrEngine object with a recognize method. See the browser usage guide for details.

A note on OCR approaches

These days, its common to apply the term "OCR" to both traditional approaches and newer LLM-based document understanding models.

The LiteParse OCR API is designed specifically for approaches that return text with bounding boxes.

If you are trying to integrate a method that doesn't return bounding boxes, you will have to generate dummy bounding boxes.


Src Content Docs Liteparse Guides Parsing Urls (`docs/src/content/docs/liteparse/guides/parsing-urls.md`)


title: Parsing URLs sidebar: order: 8

To parse remote files, LiteParse supports both CLI and library usage for reading bytes and streams. The CLI can download them with any tool you like and pipe the bytes to lit parse using - as the file argument, while the libraries can fetch the bytes directly and pass them to the parser.

CLI usage

bash
# Parse a remote PDF
curl -sL https://example.com/report.pdf | lit parse -

# With options
curl -sL https://example.com/report.pdf | lit parse --no-ocr --format json -

# Save to a file
curl -sL https://example.com/report.pdf | lit parse -o report.txt -

The - argument tells LiteParse to read from stdin instead of a file path. Any tool that writes to stdout works — curl, wget, aws s3 cp - -, etc.

Library usage

The TypeScript library accepts Buffer/Uint8Array directly, so you can handle the download however you like.

For example, using fetch in a Node.js environment:

typescript
import { LiteParse } from "@llamaindex/liteparse";

const response = await fetch("https://example.com/report.pdf");
const buffer = Buffer.from(await response.arrayBuffer());

const parser = new LiteParse({ ocrEnabled: false });
const result = await parser.parse(buffer);
console.log(result.text);

Src Content Docs Liteparse Guides Server Usage (`docs/src/content/docs/liteparse/guides/server-usage.md`)


title: Server Usage sidebar: order: 10

liteparse-server is an Express server that exposes @llamaindex/liteparse as an HTTP parsing backend. It lets you run LiteParse as a standalone service, which is useful when your application isn't Node.js, when you want to share one parsing instance across multiple clients, or when you need a parsing endpoint behind your own infrastructure.

The server ships in two flavours:

  • Slim — a minimal build with no external dependencies, ideal for getting started or embedding in your own stack.
  • Full — an all-in-one setup with built-in Redis caching and rate limiting, OpenTelemetry tracing (Jaeger), and metrics (Prometheus + Grafana), wired together with Docker Compose.

Both expose the same two endpoints: POST /parse and POST /screenshots.

Requirements

  • Bun ≥ 1.0 (or Node.js)
  • Docker and Docker Compose (only required for the full server setup)

Slim server

src/slim.ts is a minimal version of the server that removes caching and observability while keeping rate limiting and logging. It needs no Redis, no OpenTelemetry collector, and no supporting services.

Running locally

After cloning the GitHub repository:

bash
git clone https://github.com/run-llama/liteparse-server
cd liteparse-server

You can run the API server with either Bun or Node:

bash
bun run start-slim:bun
# or with Node
npm run start-slim:node

The server listens on port 5000.

Running with Docker

Pre-built image

You can pull the pre-built LiteParse (slim) server image from the GitHub Container Registry:

bash
docker pull ghcr.io/run-llama/liteparse-server:main

You can then run it exposing port 5000:

bash
docker run -p 5000:5000 ghcr.io/run-llama/liteparse-server:main

Build locally

If you clone the repository, the provided slim.Dockerfile produces a self-contained image:

bash
# Build the image
docker build -f slim.Dockerfile -t liteparse-server-slim .

# Run exposing port 5000
docker run -p 5000:5000 liteparse-server-slim

The API is then available at http://localhost:5000.

Full server

For a production-style deployment with caching, rate limiting, distributed tracing, and metrics, follow the Docker Compose example in examples/docker-compose. It brings up the server alongside Redis, the OpenTelemetry Collector, Jaeger, Prometheus, and Grafana.

API specification

Base URL: http://localhost:5000

`POST /parse` — parse a single file

Parses a single document and returns either structured page data or plain text.

Form fields:

Field Type Required Description
file file Yes The document to parse
config string No JSON-serialized LiteParseConfig options

Query parameters:

Parameter Type Default Description
text boolean false If true, returns text/plain; otherwise application/json with a pages array

Responses:

  • 200 text/plain — extracted text (when text=true)
  • 200 application/json — { "pages": [...] } (when text=false)
  • 400 — missing file
  • 429 — rate limit exceeded (only if rate limiting is configured)

`POST /screenshots` — screenshot pages of a document

Renders document pages as PNG images and streams them back as newline-delimited JSON (NDJSON).

Form fields:

Field Type Required Description
file file Yes The document to screenshot
config string No JSON-serialized LiteParseConfig options

Query parameters:

Parameter Type Default Description
pages string all Comma-separated 1-based page numbers to screenshot (e.g. 1,2,3)

Response 200 application/x-ndjson — one JSON object per line:

json
{
  "index": 0,
  "mimetype": "image/png",
  "data": "<base64>",
  "page_number": 1,
  "height": 1056,
  "width": 816
}

Example usage with `curl`

Parse a file and get JSON pages:

bash
curl -X POST http://localhost:5000/parse \
  -F "file=@path/to/document.pdf"

Parse a file and get plain text:

bash
curl -X POST "http://localhost:5000/parse?text=true" \
  -F "file=@path/to/document.pdf"

Pass a LiteParseConfig (e.g. enable OCR):

bash
curl -X POST http://localhost:5000/parse \
  -F "file=@path/to/document.pdf" \
  -F 'config={"ocrEnabled":true}'

Screenshot specific pages and stream NDJSON:

bash
curl -X POST "http://localhost:5000/screenshots?pages=1,2,3" \
  -F "file=@path/to/document.pdf"

Src Content Docs Liteparse Guides Visual Citations (`docs/src/content/docs/liteparse/guides/visual-citations.mdx`)


title: Visual Citations with Bounding Boxes sidebar: order: 7

When building agents or RAG workflows, it is often not enough to parse text and call it done. Frequently, users and applications will require you to show where that text came from.

LiteParse gives you spatial coordinates for every text item, plus page screenshots, so you can highlight exact regions on the rendered page.

How bounding boxes work

When you parse a document with JSON output, each page includes a list of text items — every extracted text element with its position (x, y, width, height) and content.

bash
lit parse document.pdf --format json
json
{
  "pages": [
    {
      "page": 1,
      "width": 612,
      "height": 792,
      "text": "Revenue grew 15% ...",
      "text_items": [
        {
          "text": "Revenue grew 15%",
          "x": 72,
          "y": 200,
          "width": 150,
          "height": 12
        }
      ]
    }
  ]
}

Coordinates are in PDF points (1 point = 1/72 inch). Origin is the top-left corner of the page, with X increasing right and Y increasing down.

JSON output is snake_case (text_items, page). The Node bindings use camelCase (textItems, pageNum) and Python uses snake_case (text_items, page_num). The examples below use each language's own naming.

Library usage

The library lets you parse for bboxes and generate screenshots in a single script. For example, searching for "0°C to 70°C" and showing where it appears:

### TypeScript
typescript
import { LiteParse, searchItems } from "@llamaindex/liteparse";

const parser = new LiteParse({ outputFormat: "json" });
const result = await parser.parse("report.pdf");

for (const page of result.pages) {
  const matches = searchItems(page.textItems, { phrase: "0°C to 70°C" });
  for (const match of matches) {
    console.log(`Found "${match.text}" at (${match.x}, ${match.y}) ${match.width}×${match.height}`);
  }
}

Python

python
from liteparse import LiteParse, search_items

parser = LiteParse()
result = parser.parse("report.pdf")

for page in result.pages:
    matches = search_items(page.text_items, "0°C to 70°C")
    for match in matches:
        print(f'Found "{match.text}" at ({match.x}, {match.y}) {match.width}×{match.height}')

Converting coordinates to image pixels

Text item coordinates are in PDF points, but screenshots are in pixels. To draw highlights on a screenshot, you need to scale the coordinates:

typescript
const scaleFactor = dpi / 72; // PDF points -> pixels at your chosen DPI

function itemToPixels(item, dpi = 150) {
  const scale = dpi / 72;
  return {
    x: item.x * scale,
    y: item.y * scale,
    width: item.width * scale,
    height: item.height * scale,
  };
}

For example, at the default 150 DPI the scale factor is 150 / 72 ~ 2.08, so a text item at (72, 200) maps to pixel (150, 416).

Full example: highlighting citations with sharp

Here's a complete workflow that parses a PDF, searches for a phrase, and draws yellow highlight boxes on the page screenshot:

text
/* Detailed source-code truncated for AI context efficiency. */

Running this script on a PDF will produce new images with the search phrase highlighted, showing exactly where the information was found on the page.

CLI usage

Parse to JSON to get bounding boxes:

bash
lit parse document.pdf --format json -o result.json

Generate page screenshots alongside:

bash
lit screenshot document.pdf -o ./screenshots

From there, you (or an agent) can process the resulting JSON and screenshots as needed using any tools available.

Tips

  • Use the same dpi value for both parse() and screenshot(). The default is 150 for both.
  • Page width and height in the JSON are in PDF points, matching the coordinate space. Use these if you need to normalize coordinates to percentages.
  • For multi-word phrase search, iterate textItems and check adjacent items — a phrase may span multiple text items.
  • To highlight a whole heading, paragraph, or table rather than individual words, parse with extract_blocks. Each block carries one bbox covering every line that fed it, so you can outline a region without stitching text items together yourself — and table cells carry their own boxes for cell-level citations.

--- METRICS ---