# zpdf: a PDF text extractor written in Zig, at alpha stage

> zpdf pulls text out of PDF files using memory-mapped reads, SIMD-accelerated parsing and an arena allocator, and ships a CLI and a Python binding alongside the Zig library. The README marks it as an early alpha version and lists precisely where it falls short of pdfium and MuPDF, including no rendering, no encrypted PDF support and only partial CID font coverage.

**Lulzx/zpdf** — Zero-copy PDF text extraction library written in Zig. High-performance, memory-mapped parsing with SIMD acceleration.

- Repository: https://github.com/Lulzx/zpdf
- Website: https://lulzx.com/zpdf
- Stars: 921 · Forks: 37
- Language: Zig
- License: CC0-1.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/lulzx-zpdf

## An alpha-stage extractor built around zero-copy reads

zpdf is a PDF text extraction library written in Zig, and the README titles it as an alpha stage, an early version. The feature list explains where the effort goes: memory-mapped file reading with zero-copy where possible, streaming text extraction with efficient arena allocation, and a set of decompression filters covering FlateDecode, ASCII85, ASCIIHex, LZW and RunLength. Font handling covers WinAnsi, MacRoman and ToUnicode CMap, and the parser handles both XRef tables and XRef streams, the latter being what PDF 1.5 and later use. Two design choices stand out. Error handling is configurable, with strict and permissive modes, which matters because a real-world corpus contains files that a strict parser rejects outright. And there is a dedicated module for SIMD-accelerated parsing, which is the mechanism behind the performance claim in the project description rather than a marketing line. The source tree is small enough to read in one sitting, with a root document API, a CLI entry point, separate modules for the object parser, XRef handling, page tree resolution, decompression, encoding, the Adobe Glyph List, CFF and Type1 fonts, the content stream interpreter, the structure tree, text layout, Markdown export and the SIMD code, plus C ABI and WASM export modules for foreign function interface use.

## Three reading-order strategies and how the default picks between them

Extracting text is easy and extracting it in the order a human reads it is not, and zpdf treats this as its central problem. The default path prioritises complete text extraction. The first strategy is the structure tree, preferred for tagged PDFs, using marked-content identifiers in the document's semantic structure, with a safety net: if the structured result contains too little of the page's stream text, zpdf keeps the more complete stream-order result instead. The second is stream order, the fallback for untagged content, extracted in raw PDF content-stream order, and the Python fast mode uses that same order while bypassing structure-tree processing. The third is geometric layout, opt-in through the reading-order flag on the command line or the reading_order argument in Python, which analyses estimated span positions and columns to approximate visual order. The README calls this path experimental. The comparison table is blunt about the trade-offs: the structure tree gives you author-provided semantic order but needs usable tagging and may be incomplete, stream order is fast and preserves content completeness but may not match visual order, and the geometric layout can approximate visual and column order but relies on estimated bounds and may fail on complex layouts.

## The gap list against pdfium and MuPDF is explicit

The README includes a feature comparison against pdfium and MuPDF, which is more useful than most project comparisons because it names the missing parts. On fonts, WinAnsi, MacRoman and ToUnicode CMap are all supported, but CID fonts of Type0 are marked partial, with a footnote saying they work when the CMap is embedded directly. On compression, FlateDecode, LZW and ASCII85 and ASCIIHex are supported, while JBIG2 and JPEG2000 are not. Two larger absences follow: encrypted PDFs are not supported at all, and there is no rendering. The rendering gap is the one that decides the use case, since zpdf extracts text and does not draw pages. The comparison also notes that zpdf's span bounds use text positions plus an estimated width and are not exact glyph or word bounds, unlike pdfium and MuPDF, and that competitor capabilities refer to their public APIs and may vary by version. The README's own guidance is to use zpdf for batch processing, tagged PDFs in the PDF/UA family, simple text extraction and Zig integration, pdfium for browser integration and full PDF support with proven stability, and MuPDF for complex visual layouts and rendering.

## The CLI is the shortest path to a first extraction

Building requires Zig 0.15.2 or later, and the two build commands cover everything:

```bash
zig build              # Build library and CLI
zig build test         # Run tests
```

The CLI then exposes the obvious operations:

```bash
zpdf extract document.pdf              # Extract all pages (uses structure tree for reading order)
zpdf extract -p 1-10 document.pdf      # Extract pages 1-10
zpdf extract -o out.txt document.pdf   # Output to file
zpdf info document.pdf                 # Show document info
zpdf bench document.pdf                # Run benchmark
```

The page range and output file flags are what a batch job needs, and the extract command uses the structure tree for reading order by default. There is also a Markdown export path in the source tree for structured PDFs, which is not exposed in the CLI examples shown in the README but is listed among the library features. For a first use, the sensible path is to build the release binary, extract a document you know well, and read the output, because the difference between the structure tree order and the stream order is only visible on a file you can check against the original.

## Python bindings, and the stale binary trap in a checkout

The library is exposed to Python through cffi, and the binding is deliberately thin:

```python
import zpdf

with zpdf.Document("file.pdf") as doc:
    print(doc.page_count)

    text = doc.extract_page(0)
    all_text = doc.extract_all()
    fast_text = doc.extract_all(mode="fast")
```

The zero-copy memory open is available too, and the README flags it as unsafe semantics for other language bindings. Getting the Python side running needs the shared library built first, and the examples directory contains basic.py, extract_to_file.py and parallel.py:

```bash
zig build -Doptimize=ReleaseFast
PYTHONPATH=python python3 examples/basic.py
```

An installable platform-specific wheel can be built from the current Zig library with the build frontend, and there is a detail worth knowing if you develop from a checkout: the Python loader prefers the ZPDF_LIB environment variable and the zig-out/lib directory over any packaged library, so tests cannot silently run against a stale binary. That is a small decision that saves a confusing afternoon.

## The benchmark is framed as a local diagnostic

The benchmark section is unusually careful about its own limits, and that framing is part of what to know before you trust any number. Building with the release optimization flag and running the bench step produces five zpdf extractions plus one MuPDF comparison when mutool is installed, and the README asks you to treat that as a local diagnostic rather than a controlled cross-tool benchmark. It also asks you to record the zpdf revision, the Zig and MuPDF versions, the hardware, the input checksum and the run policy when you publish results, which is the discipline a published number needs. The corpus and accuracy tooling is documented separately in the benchmark README, and the full methodology uses the olmOCR-Bench, veraPDF and PDF.js corpora to keep ground truth accuracy separate from compatibility behaviour. The initial measured findings are published in the repository and they are not flattering: they identify concrete reading-order and dense-text gaps. Reading those documents is the fastest way to find out whether zpdf suits your corpus before you commit to it.

## Conclusion

zpdf fits a team that needs batch text extraction from tagged PDFs, that wants to link a PDF parser into a Zig codebase without pulling a large C++ dependency, and that can accept an alpha-stage library with the gaps the README lists. It does not fit anyone who needs rendering, encrypted PDFs, JBIG2 or JPEG2000 images, or complete Type0 CID font handling, since the comparison table marks all of those as unsupported or partial. Verify first that your documents are FlateDecode or LZW based and untagged or properly tagged, then run the built-in benchmark against your own corpus rather than a public sample. The licence field on the repository says CC0-1.0, the README describes it as public domain, release v0.2.0 was published on 2026-08-11, and the last push was on 2026-08-13.

## FAQ

### What is zpdf and what language is it written in?

zpdf is a PDF text extraction library written in Zig. It uses memory-mapped file reading with zero-copy where possible, streaming extraction with arena allocation, SIMD-accelerated parsing, and it ships a CLI, a C ABI module, a WASM module and Python bindings built on cffi.

### How do I build zpdf?

You need Zig 0.15.2 or later. The README gives two commands: zig build to build the library and CLI, and zig build test to run the tests. For a release binary the build uses zig build -Doptimize=ReleaseFast.

### What reading order does zpdf use when extracting text?

The default prioritises complete extraction. It prefers the structure tree for tagged PDFs, but keeps the more complete stream-order result if the structured one contains too little of the page text. Stream order is the fallback for untagged content, and an experimental geometric layout mode can be enabled with the reading-order flag on the command line or the reading_order argument in Python.

### What can zpdf not do compared with pdfium and MuPDF?

The README's comparison table marks CID fonts of Type0 as partial, JBIG2 and JPEG2000 as unsupported, encrypted PDFs as unsupported, and rendering as absent. Its span bounds are estimated rather than exact glyph or word bounds. The README points to pdfium for full PDF support and to MuPDF for complex layouts and rendering.

### Under which licence is zpdf released?

The licence field on the repository says CC0-1.0, and the README states CC0, Public Domain, with a LICENSE file at the root of the repository.

## Sources

- [License: CC0-1.0](https://github.com/Lulzx/zpdf/blob/main/LICENSE)
- [Lulzx/zpdf on GitHub](https://github.com/Lulzx/zpdf)
- [Project website](https://lulzx.com/zpdf)
- [README](https://github.com/Lulzx/zpdf/blob/main/README.md)
- [Releases](https://github.com/Lulzx/zpdf/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lulzx-zpdf
