PDF Inspector separates native text from pages that need OCR
PDF Inspector first classifies a document as text based, scanned, image based, or mixed. It can then extract positioned text, infer reading order, and produce Markdown with headings, lists, links, tables, and page breaks. Page-level results tell a caller which pages may need OCR. This is useful in an ingestion pipeline because native text should not be sent through an image recognizer merely because one page is a scan.
The core is Rust, with packages for Python, Node.js, browser WebAssembly, and native Rust callers. Command-line programs expose detection and conversion as JSON or Markdown. Our checkout contained 312 files and about 106,900 source lines in 15.6 MB. That line count includes bindings, fixtures, generated or supporting code as captured by the harness; it describes repository scale rather than parser speed or extraction quality.
Native extraction stays light until a page reaches OCR
The default parser reads PDF content streams, font mappings, drawing operations, links, and form fields without loading an OCR model. Its Markdown layer groups lines, detects columns, uses font sizes for heading levels, identifies monospace blocks, and attempts both rectangle-based and alignment-based tables. A browser can run that parsing through WebAssembly without sending the document to a server.
Selective OCR is a native feature with more moving parts. Rust, the CLI, Python, and Node.js can render selected pages through PDFium and recognize them with PP-OCRv6 Small through ONNX Runtime. Those libraries and model files remain external. The 51 MB from our Python install did not include an activated OCR runtime or downloaded model cache, so operators should budget and pin those assets separately.
What happened when we ran it
Our fresh Python 3.12 Debian sandbox installed commit 23cf1ad in 474 seconds. The environment added 36 packages and occupied 51 MB. The build then succeeded in 8 seconds. The repository had 6 CI workflow files, a tests directory, and no Dockerfile. Pip-audit reported 0 known vulnerabilities among the installed Python packages.
Pytest ran for 59 seconds and reported 100 passed and 1 failed out of 101, plus 9 passing subtests. The failure came from the parametrized fixture encrypted-secret123.pdf. Calling pdf_inspector.process_pdf raised ValueError: PDF is encrypted. The log tail does not show whether a password-aware API exists elsewhere or why that fixture was expected to pass, so our finding stops at the observed exception.
Encrypted files need a policy before ingestion starts
A document pipeline cannot treat that exception as a rare curiosity. Password-protected PDFs appear in financial, legal, and customer-upload workflows. Decide whether the service rejects them with a useful message, accepts a password through a separate trusted channel, or routes them to another parser. Never record an empty Markdown string as a successful extraction because downstream search will quietly omit the document.
Open issue 254 asks for an OCR routing guide with confidence thresholds and fallback behavior for corrupted and encrypted PDFs. The API already reports classification, confidence, and page suggestions, but the project does not yet prescribe the business decision around those fields. Our 100 passing tests show wide exercised behavior; the 1 encrypted fixture failure shows why callers still need a document-level error state.
Font and table output must be checked against the source
PDF text is drawing instructions, not a semantic document tree. PDF Inspector reconstructs structure from coordinates, fonts, operators, and shapes. That can work well while still producing a valid-looking wrong value. Issue 457 reports Type0 Identity-H text with a ToUnicode map decoding individual characters incorrectly without an error. Issue 456 reports invisible or occluded text surviving extraction in some cases.
Tables carry higher stakes. Issue 419 describes distinct numeric columns merging into one cell for a row in a customs statistics PDF. A Markdown table can be syntactically perfect and numerically false. For invoices, filings, or research data, compare cell counts and critical values against rendered pages. The 59-second suite is a useful baseline for the project checkout, but your corpus needs regression fixtures for its fonts, languages, scans, and table styles.
Four language surfaces widen the testing matrix
Python accepts a path and returns a result object. Node.js reads bytes and exposes synchronous native extraction plus asynchronous OCR. Rust offers option builders and feature-gated OCR, while browser WebAssembly works with byte arrays and stays on extraction. The CLI can emit raw Markdown, positioned items, compact output, selected pages, and classification JSON. This range makes the parser easier to place in an existing stack.
It also creates packaging choices. Native Node builds are published for named Linux, macOS, and Windows targets. OCR requires runtime library paths and model caching. A browser worker avoids server uploads but cannot follow the same OCR route described for native packages. Keep one set of golden PDFs and compare outputs across the exact binding and version you deploy instead of assuming every surface behaves identically.
Active releases do not remove corpus-specific risk
GitHub listed 16,734 stars, 182 combined issues and pull requests, and a last push on August 21, 2026. Release v1.15.0 arrived on August 17 with selective OCR packages across Rust, Python, Node.js, and the CLI. Open reports from the following week cover character decoding, hidden text, OCR confidence, and table structure, showing both active testing and unfinished edges.
PDF Inspector earns a serious trial for local extraction because its page routing is practical and its APIs span common runtimes. The measured 100-of-101 result is encouraging without being a clean pass. Use it behind explicit error handling, keep encrypted files out of the success path, and judge extracted content against real rendered pages before any system treats the Markdown as authoritative.


