avvertix/liteparse-php

PHP FFI bindings for LiteParse, fast local PDF and document parsing with spatial text extraction

Maintainers

Package info

github.com/avvertix/liteparse-php

pkg:composer/avvertix/liteparse-php

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-07-07 10:45 UTC

This package is auto-updated.

Last update: 2026-07-07 11:38:06 UTC


README

Native PHP bindings for LiteParse, local PDF and document parsing with spatial text extraction powered by Rust and PDFium.

Given a PDF this package extracts text with bounding boxes and renders it as structured JSON, plain text, or layout-aware Markdown (headings, lists, tables, figure references). The parsing happens directly from PHP, via a compiled Rust cdylib loaded through PHP's FFI extension.

Note

The native bindings are tested only for PDF files. Support for DOC/DOCX/XLS/XLSX/PPT/PPTX, and images is not tested and not provided so far.

Requirements

  • PHP 8.3+ with ext-ffi enabled (ffi.enable=On in php.ini; CLI defaults to on)
  • A compiled liteparse_php native library for your platform (see Installation)
  • Optional, for OCR: an HTTP OCR server implementing LiteParse OCR_API_SPEC.md (reference servers for EasyOCR/PaddleOCR/SuryaOCR ship in the LiteParse repo). This binding does not bundle the Tesseract engine as would increase the complexity of the build and distribution pipeline, point Config::$ocrServerUrl at a server instead.

Installation

Get the package via Composer and install the native library for your platform.

composer require avvertix/liteparse-php
vendor/bin/liteparse-php install

install downloads the compiled liteparse_php library and its PDFium dependency for your platform from the package's GitHub Releases into vendor/avvertix/liteparse-php/lib/. The specific installed versions are recorded in a natives.lock file in the root of your project, commit this alongside composer.lock to install the same version of the compiled binary. Run vendor/bin/liteparse-php update after upgrading the package to fetch the matching native library.

Quick start

use LiteParse\Config;
use LiteParse\OutputFormat;
use LiteParse\LiteParse;

$parser = new LiteParse(new Config(outputFormat: OutputFormat::Markdown));

$result = $parser->parseFile('/path/to/document.pdf');

echo $result->pageCount();   // int
echo $result->text();        // plain text, "--- Page N ---" headers
echo $result->markdown();    // headings/lists/tables/figure refs reconstructed from layout
echo json_encode($result->json());  // structured per-page text items: bbox + font size + fill/stroke color, per item
echo json_encode($result->lines());  // structured per-page projected lines: merged bbox + style + column geometry

json() returns liteparse's full per-item TextItem — not the lean {text, x, y, width, height} shape the upstream lit CLI's own --format json produces. Each item carries its bounding box together with font_size and fill_color/stroke_color (ARGB hex, e.g. "ff000000") on the same record, plus rotation, links, strikethrough, and OCR confidence where applicable. Font/color fields are only populated for native PDF text; OCR-derived items carry null there instead.

lines() sits between json() and markdown(): each entry is a merged visual line (one or more TextItems sharing a baseline) carrying its own bounding box, dominant font/style, and region_path — the xy-cut column/region position liteparse uses internally to group paragraphs and tables. Each line's spans field keeps the original TextItems that merged into it, so per-run font/color survives even where the line's own text concatenates multiple items. Unlike markdown(), nothing here is reformatted or dropped when the heuristic table/heading detection misfires — you get the raw geometry and can reconstruct rows/columns/headings yourself from region_path and bbox positions. There is no heading/paragraph/list "role" label at this layer.

Every ParseResult accessor (text(), markdown(), json(), lines()) renders on demand from the same underlying parsed pages.

Features

  • LiteParse::parseFile() / parseBytes() — parse from a file path or an in-memory buffer (e.g. a PDF downloaded over the network).
  • LiteParse::isComplexFile() / isComplexBytes() — a cheap per-page pre-check (no OCR, no rendering) reporting whether each page looks scanned, sparse, garbled, or image-heavy — useful for deciding whether a document needs OCR before committing to a full parse.
  • LiteParse::screenshotFile() / screenshotBytes() — render selected pages (or the whole document) to PNG bytes.
  • ParseResult::search() — search already-parsed text for phrase matches, with bounding boxes, merged across text items that were split mid-phrase.
// Complexity pre-check
$stats = $parser->isComplexFile('scan.pdf');
$needsOcr = array_filter($stats, fn ($page) => $page['needs_ocr']);

// Screenshots
$screenshots = $parser->screenshotFile('doc.pdf', pageNumbers: [1, 2]); // null = all pages
foreach ($screenshots as $shot) {
    file_put_contents("page-{$shot->pageNumber}.png", $shot->bytes);
}

// Search
$result = $parser->parseFile('doc.pdf');
foreach ($result->search('quarterly revenue') as $match) {
    printf("page %d at (%.0f, %.0f)\n", $match['page_number'], $match['x'], $match['y']);
}

See examples/ for runnable scripts.

Configuration

Config mirrors liteparse's Rust LiteParseConfig field-for-field:

Field Default Notes
ocrLanguage 'eng' Tesseract-format language code
ocrEnabled false Requires ocrServerUrl — this binding has no built-in OCR engine
ocrServerUrl null HTTP OCR server URL
ocrServerHeaders [] [[name, value], ...] sent with every OCR request
maxPages 1000
targetPages null e.g. "1-5,10,15-20"; null = all pages
dpi 150.0 Used for OCR and screenshots
outputFormat OutputFormat::Json Informational only in this binding — text()/markdown()/json() are always available regardless
preserveVerySmallText false
password null For encrypted/protected documents
quiet true Suppresses liteparse's stderr progress logging (Rust default is false)
numWorkers 1 Concurrent OCR requests to the HTTP server
imageMode ImageMode::Placeholder Affects markdown() image references only
extractLinks true Hyperlinks as [text](url) in markdown
ocrFailureFatal true Abort the whole parse on systemic OCR failure vs. return degraded results
ocrHedgeDelaysMs [] Request-hedging schedule for the HTTP OCR engine
emitWordBoxes false Per-word sub-boxes on each text item (roughly doubles payload size)

How it works

LiteParse Rust create is exposed as C library to be consumed by the PHP Foreign Function Interface (FFI). The shared library is defined in rust/ exposing handles for the parser/result/screenshot-list lifecycle, plus configuration and exceptions. LiteParse's API is async (tokio-based); the FFI layer owns a single process-wide tokio runtime and block_ons each call, since PHP FFI calls are synchronous. cbindgen generates the committed include/liteparse_php.h header that PHP's FFI::cdef() loads.

PDFium (a separate native dependency liteparse links against) is discovered at runtime by pdfium-sys's loader relative to whichever shared library loaded it.

Development

./scripts/build.sh          # cargo build --release, stage lib/
composer install
composer test               # PHPUnit, against the compiled library
composer lint               # PHPStan

Adding a new FFI function: add the extern "C" function in rust/src/ffi/, rebuild (cbindgen regenerates include/liteparse_php.h automatically via build.rs), add the corresponding @method annotation to LiteParseFfi, and wrap it in a PHP class.

Building from source

If you're working in this repository directly (or no prebuilt release exists yet for your platform):

./scripts/build.sh release   # cargo build --release + stage lib/liteparse_php.* and lib/libpdfium.*
composer install
composer test

scripts/build.sh compiles the Rust crate in rust/ and copies the resulting native library, plus its PDFium runtime dependency, into lib/.

License

The project is dual licenced. The PHP wrapper code is licenced under MIT. The Rust binding to expose via FFI are licenced under Apache-2.0.