# pdftext pulls structured text out of PDFs on an Apache licence

> pdftext is a thin wrapper around pypdfium2 that extracts plain text or JSON blocks, lines and spans from a PDF, with a CLI, a Python API and a table extraction entry point. It exists mainly to give PyMuPDF-style output without the AGPL licence, and it inherits PDFium's lack of thread safety.

**datalab-to/pdftext** — Extract structured text from pdfs quickly

- Repository: https://github.com/datalab-to/pdftext
- Stars: 733 · Forks: 81
- Language: Python
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/datalab-to-pdftext

## The AGPL workaround that shapes everything else

The pitch for pdftext fits in the first line of the README: text extraction like PyMuPDF, but without the AGPL license. PyMuPDF is a capable extractor, but its licence makes it awkward inside a closed source product, and pdftext is the answer to that specific constraint. Everything else follows from how it is built. The project is a thin wrapper around pypdfium2, so the actual rendering and text extraction is done by PDFium, the browser engine's PDF library, and pdftext sits on top with a friendlier surface. The repository description says it extracts structured text from pdfs quickly, the package metadata names it Apache-2.0, and the README states that being built on pypdfium2 is what makes it fast and accurate while staying Apache licensed. The package name on PyPI is pdftext, it needs Python 3.10 or newer, and installation is a single pip command. The audience is Python developers building document pipelines who need structure rather than a wall of text, and who have a licence requirement that rules out the obvious alternative.

## Plain text on the command line, structured JSON with a flag

The command line entry point is registered in the package metadata as pdftext, pointing at pdftext.scripts.extract_text:extract_text_cli. The plain text mode writes an extracted file.

```bash
pdftext PDF_PATH --out_path output.txt
```

PDF_PATH must be a single PDF file, and if --out_path is not given the text goes to stdout instead. The same run can produce JSON with one extra flag.

```bash
pdftext PDF_PATH --out_path output.txt --json
```

That JSON is the reason to use this library rather than any other text dumper. The output is a list with one item per page, in order, and each page carries a bbox in [x1, y1, x2, y2] format, the page rotation in degrees, the page index, and the blocks that make up the text, where a block is approximately a paragraph. Each block has a bbox and a list of lines, each line has a bbox and spans, and each span carries its text encoded in utf-8, its rotation in radians taken from PDFium's character angle, its bbox, the character index range of the span in the PDF, and font information taken straight from the PDF: size, weight, name, and flags in the PDF 1.7 Section 5.7.1 font descriptor format. The --keep_chars flag keeps individual characters in the JSON output, which is what you want when you need the character level index. If the page is rotated, the bounding boxes come back relative to the rotated page.

## The flags that change what comes out

The CLI options are documented one by one and they map cleanly onto the problems that show up in real documents. --sort attempts to sort the output in reading order, which is what you want for multi column layouts, though the word attempt matters because it is a best effort. --keep_hyphens keeps hyphens in the output, and without it hyphens are stripped and the split words are joined, so a document that uses hyphens for line breaks reads correctly by default. --page_range takes a comma separated list of pages in the form 0,5-10,12, which lets you pull a section of a long document without splitting the file first. --workers sets the number of parallel workers. --flatten_pdf merges form fields into the PDF, which is the fix for documents whose visible text comes from interactive form fields. --password opens an encrypted document, and the same option exists on the Python side as a password argument to any of the extraction functions. Two of these deserve a warning. --sort is a heuristic on the underlying page structure, so a two column paper will need checking rather than trusting. And --keep_hyphens works in the opposite direction from what you might assume: the default output is the joined and de-hyphenated one, which changes words for languages where a hyphen is meaningful.

## Programmatic use, including a table extraction path

The library exposes three functions, all in pdftext.extraction. Plain text is the simplest.

```python
from pdftext.extraction import plain_text_output

text = plain_text_output(PDF_PATH, sort=False, hyphens=False, page_range=[1,2,3]) # Optional arguments explained above
```

Structured blocks and lines come from dictionary_output, with the keep_chars switch.

```python
from pdftext.extraction import dictionary_output

text = dictionary_output(PDF_PATH, sort=False, page_range=[1,2,3], keep_chars=False) # Optional arguments explained above
```

The third one, table_output, is the interesting entry point because it does not do the table detection itself. You hand it the coordinates of tables you have already located, along with the size of the image they were detected in.

```python
from pdftext.extraction import table_output

table_inputs = [
  # Each dictionary entry is a single page
  {
    "tables": [[5,10,10,20]], # Coordinates for tables on the page
    "img_size": [512, 512] # The size of the image the tables were detected in
  }
]
text = table_output(PDF_PATH, table_inputs, page_range=[1,2,3])
```

So detection and extraction are separate problems here. If you already run a layout model that produces table boxes, pdftext will read the text inside them and nothing more. The README also points at pdftext.extraction._get_pages as the place to start if you need to go deeper, and it is candid about the architecture: pdftext is a pretty thin wrapper around pypdfium2, so the underlying library's documentation is where the real reference lives. Settings live in pdftext/settings.py and every one of them can be overridden with an environment variable.

## pdfium is not thread safe, and that shapes your architecture

This is the section to read before you parallelise anything. The README states plainly that pdfium is not thread-safe, and that you must not call pdftext from multiple threads at once, even on different files, because extractions will fail or corrupt each other. The advice is to use the built-in workers option, which is process based, or your own process pool. The workers option has its own conditions, and they are documented rather than left to be discovered. Parallel extraction only starts when each worker would get at least ten pages, and that threshold is configurable through the PDFTEXT_WORKER_PAGE_THRESHOLD environment variable, so a short document runs serially no matter what you pass. On macOS and Windows, where the spawn start method is used, a script that calls pdftext with workers must be wrapped in an if __name__ == "__main__": guard, following the standard multiprocessing rules. File-like inputs cannot be sent to workers at all and run serially, so for parallel work you pass a path rather than an open file handle. In other words, the concurrency story is process based with a page threshold, not thread based, and a shared service that fans out to threads will corrupt its own results.

## Right to left text comes back in visual order

The language support section is unusually explicit about where the output diverges from what a reader expects. pdftext extracts whatever character ordering and Unicode mapping the PDF provides through PDFium. Left to right scripts such as CJK, Cyrillic, Greek and Vietnamese extract correctly. Right to left scripts, meaning Arabic and Hebrew, are returned in the order PDFium reports them, which is usually visual order and therefore reversed relative to logical reading order. The README names the reason directly: PDFium does not perform bidi reordering, and that is the main extraction quality difference between pdftext and PyMuPDF for right to left documents. If your corpus is Arabic or Hebrew and you care about the order of the words, this library will hand you text that reads backwards, and no flag in the documented list fixes it, since the sorting option operates on page layout rather than on script direction. Complex script fidelity, with Indic conjuncts for instance, and emoji depend entirely on the ToUnicode map in the PDF, and a broken map produces the same garbled output in any extractor. That last point is the useful one for triage: when the text is nonsense, the cause is often the file rather than the tool.

## Where pdftext loses to PyMuPDF, and what it gains

The README carries its own benchmark table, run on an Apple Silicon Macbook without multiprocessing, comparing pymupdf, pdftext and pdfplumber. PyMuPDF is the reference at 0.34 seconds per document, pdftext comes in at 0.69 seconds, and pdftext's alignment score against PyMuPDF is 97.54 percent. The table is truncated in the published README, so the pdfplumber row is not visible here, and the comparison was deliberately scoped: the author benchmarked pymupdf because it extracts blocks and lines, pdfplumber because it extracts words and bboxes, and skipped pypdf because it does not provide individual character, line and block information with bounding boxes. So the honest reading is that pdftext is roughly twice as slow as PyMuPDF on that measurement and agrees with it on 97.54 percent of the alignment check, while the license is the reason to accept that. What you get instead of PyMuPDF's licence is Apache 2.0, which matters for a closed source product and does not matter at all for a research script. The project is not archived and its last push was on 2026-07-08, with v0.7.1 on the same day described as a pypdfium2 bump and v0.7.0 on 2026-07-01 as a speed up and a pypdfium2 bump, after v0.6.3 fixed a rotation issue in June 2025. The pinned pypdfium2 version is 5.10.1, and pypdfium2 is the only real dependency beyond pydantic, pydantic-settings, click and numpy, so an upgrade path is short and the dependency surface is small.

## Conclusion

pdftext is the right pick for a commercial pipeline that needs block and line level output with bounding boxes and cannot take PyMuPDF's AGPL terms, and it is a reasonable default for anyone who wants pdfium's extraction without writing the pypdfium2 calls themselves. It is the wrong tool for right to left documents where reading order matters, since the README states pdfium does not reorder bidi text, and for anyone who wants to call it from several threads at once, which the documentation says will fail or corrupt results. Before you adopt it, run the CLI on a rotated and a right to left PDF from your own corpus and compare the output with what you get today, and pin pypdfium2 to 5.10.1 as the project pins it, because the last push was on 2026-07-08 and extraction behaviour moves with the underlying renderer.

## FAQ

### How do I install pdftext and extract text from a PDF?

Install it with pip install pdftext, which needs Python 3.10 or newer, then run pdftext PDF_PATH --out_path output.txt to write plain text. Add the --json flag for structured output with blocks, lines, spans and bounding boxes.

### Can I call pdftext from multiple threads at once?

No. The README states that pdfium is not thread-safe and that calling pdftext from multiple threads, even on different files, will fail or corrupt each other. Use the workers option, which is process based, or your own process pool.

### How does pdftext handle Arabic and Hebrew PDFs?

Right to left scripts come back in the order PDFium reports them, which is usually visual order and therefore reversed relative to logical reading order, because pdfium does not perform bidi reordering. The README names this as the main extraction quality difference between pdftext and PyMuPDF.

## Sources

- [datalab-to/pdftext on GitHub](https://github.com/datalab-to/pdftext)
- [Issues](https://github.com/datalab-to/pdftext/issues)
- [License: Apache-2.0](https://github.com/datalab-to/pdftext/blob/master/LICENSE)
- [README](https://github.com/datalab-to/pdftext/blob/master/README.md)
- [Releases](https://github.com/datalab-to/pdftext/releases)

---

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