Skip to content

pypdf · pdfium · pikepdf · reportlab · pdfplumber — nothing AGPL or GPL

pdf-tooling

Every common PDF chore. One safe CLI. No copyleft underneath.

One command-line tool for merge, split, extract, rotate, PDF↔images, text, tables, compress, encrypt, metadata, watermark, OCR and Office→PDF — orchestrating a permissively licensed engine stack behind one safe, scriptable interface.

Every common PDF chore, without the footguns

Six principles, machine-checked rather than promised.

Safe by default

--dry-run shows exactly what would be written and touches nothing. Outputs never clobber without --force. Every write goes to a temp file on the target filesystem, is fsync'd, and is moved into place with os.replace. Inputs are never modified unless you ask for --in-place, which writes a .bak sidecar first.

License-clean stack

Apache-2.0 top to bottom. pypdf, pdfium, pikepdf, reportlab and pdfplumber are BSD / MIT / Apache / MPL. CI fails the build on any AGPL, GPL or LGPL package, and a unit test walks the source for forbidden imports and shell-outs — because the realistic leak in a PDF tool is a convenience subprocess call, not a dependency.

Machine-first output

Every read path emits table, json or ndjson with a declared schema_version. json is automatic when stdout is not a terminal. Designed to be a link in a shell pipeline.

Honest engine reporting

pdftooling doctor states, per port, which adapter resolved, its version, and what is missing — with an OS-aware install hint. A verb whose engine is absent exits 3 with that hint; never a stack trace, never a silently degraded result.

One page-range grammar

1-3,5,9-, even, odd, first, last, -1, !5 — parsed by one module and shared by every page-addressing verb. Learn it once.

Stable exit codes

Seven codes, uniform across every verb, asserted by a contract test. A script's recovery for "wrong password" differs from "engine missing" and from "refused", so they are different codes.

A six-layer spine, one licence guarantee

Adapters are the only modules that import an engine library or spawn a process — that is what makes the licence guarantee auditable by reading eight files.

CLI (Typer)

Parses flags, builds one frozen OperationPlan, picks a renderer. The ONLY layer that may import Typer.

Ops

Framework-free verbs, pure functions over ports. No I/O except through Safety and Ports.

Safety

The single write chokepoint — atomic write-to-temp-then-rename, no-clobber, the --dry-run gate.

Ports

typing.Protocol definitions only: StructureEngine · RasterEngine · ComposeEngine · TextEngine · OcrEngine · OfficeConverter

Adapters

The ONLY modules that import an engine library or spawn a process — which is what makes the licence guarantee auditable.

Engines

pypdf · pypdfium2 · pikepdf · reportlab · pdfplumber · Tesseract · LibreOffice.

26 shipped · 0 planned

This list is checked against pdftooling --help at every deploy.

diagnostics

doctor

Report each port's resolved adapter, version, and OS-aware install hint for what is missing.

info

Page count/sizes, encryption + permission bits, metadata, producer, font list, signature presence, per-page rotation.

version

Report the tool, runtime and engine versions.

structure

merge

Concatenate PDFs; per-input page selection via path:range; outline entry per source file.

split

One PDF → many, by fixed chunk size, explicit ranges, per page, or at top-level bookmarks.

extract

Write the selected pages to a new PDF, in the order given.

delete

Write everything except the selected pages.

rotate

Rotate the selected pages by a multiple of 90°, absolute or relative.

reorder

Rewrite page order from an explicit sequence; duplicates allowed, order preserved.

repair

Structural recovery of a damaged/malformed PDF via libqpdf's recovery parser.

linearize

Rewrite for byte-serving ("fast web view").

permissions

Report the permission bits and encryption algorithm of an encrypted PDF.

meta get

Read the document information dictionary + XMP.

meta set

Write/clear document information fields and XMP.

raster

rasterize

PDF → PNG/JPEG/TIFF/WEBP at a chosen DPI or pixel width.

compose

compose

Images → PDF. JPEG inputs embed as DCTDecode streams (byte-preserving, no re-encode).

create

Text (v1) → PDF. Markdown/HTML behind the [html] extra (Phase 2).

text

text

Extract text: fast path (pdfium) or layout-aware (pdfplumber) with per-block geometry.

tables

Detect and extract tables to CSV/JSON.

optimize

compress

Shrink: libqpdf object streams plus stream recompression (lossless), optional Pillow image downsample/recompress (lossy).

crypto

encrypt

Apply AES-256 (or RC4-128 with --legacy) with user/owner passwords and a permission set.

decrypt

Remove encryption given the correct password.

overlay

watermark

Overlay/underlay generated text across the selected pages.

stamp

Overlay/underlay an existing PDF page onto the selected pages.

ocr

Add a Tesseract text layer over untouched pixels — a text-only PDF generated per page and merged.

external

convert

Office → PDF via headless LibreOffice.

config and completion round out the surface — run pdftooling --help for the whole tree. Phase 2 (HTML/Markdown create, redaction, forms, outline editing, attachments/annotations, run recipes) is parked, additive on the same spine.

Quick Start

Install pdf-tooling; the command is pdftooling.

# Install (published on PyPI)
uv tool install pdf-tooling
# or: pip install pdf-tooling
terminal
pdftooling doctor                                                    # which engines resolved, and how
pdftooling merge a.pdf b.pdf -O merged.pdf                           # concatenate PDFs into one file
pdftooling rotate report.pdf --pages 2-4 --angle 90 -O rotated.pdf   # rotate pages 2-4 by 90°
pdftooling compress report.pdf -O small.pdf                          # lossless structural shrink

Published on PyPI as pdf-tooling — no Homebrew formula, no system package, no container image yet. Every verb listed below is shipped —pdftooling --help is the authoritative list of what actually exists.

A stable exit-code contract

Seven codes, uniform across every verb — the same contract pdftooling --version and the source both agree on.

0

OK

Success — including an empty-but-valid report. A --dry-run mirrors the code the real run would return, so it is not always 0.

1

FAILURE

The operation ran and failed — corrupt input, engine error, unwritable destination.

2

USAGE

Bad invocation — unknown flag, mutually exclusive flags, malformed page range, unknown subcommand.

3

ENGINE_MISSING

A required engine or binary is unavailable. The message always carries an install hint.

4

NO_INPUT

Valid invocation, nothing to act on.

5

REFUSED

A safety gate declined.

6

AUTH

Password required, incorrect, or of the wrong kind.

Licensing

Apache-2.0, and every engine underneath it is BSD, MIT, Apache or MPL — nothing AGPL or GPL on the call graph.

The guarantee is a CI job and a unit test, not a promise in a README: the license gate fails the build on any AGPL, GPL or LGPL package in the distributed closure, and a source-level test walks every file under src/ for forbidden imports and subprocess shell-outs.

pikepdf bundles libqpdf under MPL-2.0 — file-level copyleft, used unmodified as a dependency, and Apache-compatible on that basis. It is documented in NOTICE and the full THIRD_PARTY_LICENSES inventory.

The default install closure is 21 packages, with no GPL-licensed package at all. pyphen (GNU General Public License v2 or later (GPLv2+); GNU Lesser General Public License v2 or later (LGPLv2+); Mozilla Public License 1.1 (MPL 1.1)) enters only via the optional [html] extra (10 additional packages) — Phase 2, and not shipped in v1.

PackageVersionLicenseTier
Pygments2.21.0BSD-2-Clausedefault install
annotated-doc0.0.5MITdefault install
brotli1.2.0MIToptional · [html] extra
cffi2.1.1MIT-0default install
charset-normalizer3.5.1MITdefault install
cryptography50.0.1Apache-2.0 OR BSD-3-Clausedefault install
cssselect20.9.0BSD Licenseoptional · [html] extra
fonttools4.63.0MIToptional · [html] extra
lxml6.1.2BSD-3-Clausedefault install
markdown-it-py4.2.0MIT Licensedefault install
mdurl0.1.2MIT Licensedefault install
packaging26.3Apache-2.0 OR BSD-2-Clausedefault install
pdfminer.six20260107MITdefault install
pdfplumber0.11.10MIT Licensedefault install
pikepdf10.12.0MPL-2.0default install
pillow12.3.0MIT-CMUdefault install
pycparser3.0BSD-3-Clausedefault install
pydyf0.12.1BSD Licenseoptional · [html] extra
pypdf6.16.2BSD-3-Clausedefault install
pypdfium25.13.0BSD-3-Clause, Apache-2.0, dependency licensesdefault install
pyphen0.18.1GNU General Public License v2 or later (GPLv2+); GNU Lesser General Public License v2 or later (LGPLv2+); Mozilla Public License 1.1 (MPL 1.1)optional · [html] extra
pytesseract0.3.13Apache Software Licensedefault install
reportlab5.0.1BSD Licensedefault install
rich15.0.0MIT Licensedefault install
shellingham1.5.4ISC License (ISCL)default install
tinycss21.5.1BSD Licenseoptional · [html] extra
tinyhtml52.1.0MIT Licenseoptional · [html] extra
typer0.27.2MITdefault install
weasyprint69.0BSD Licenseoptional · [html] extra
webencodings0.6.1BSD Licenseoptional · [html] extra
zopfli0.4.3Apache Software Licenseoptional · [html] extra

Full inventory: THIRD_PARTY_LICENSES.

Technology Stack

Every version below is read straight from src/data/licenses.json, generated by make licenses and diffed in CI.

PackageVersionLicenseRole
annotated-doc0.0.5MIT
brotli1.2.0MIT
cffi2.1.1MIT-0
charset-normalizer3.5.1MIT
cryptography50.0.1Apache-2.0 OR BSD-3-ClauseAES-256 encryption backing pypdf[crypto]
cssselect20.9.0BSD License
fonttools4.63.0MIT
lxml6.1.2BSD-3-Clause
markdown-it-py4.2.0MIT License
mdurl0.1.2MIT License
packaging26.3Apache-2.0 OR BSD-2-Clause
pdfminer.six20260107MITlayout analysis engine behind pdfplumber
pdfplumber0.11.10MIT Licenselayout-aware text, tables (renders via pypdfium2, not poppler)
pikepdf10.12.0MPL-2.0repair, linearize, object-stream compression, robust encryption
pillow12.3.0MIT-CMUimage plumbing, downsampling for compression
pycparser3.0BSD-3-Clause
pydyf0.12.1BSD License
Pygments2.21.0BSD-2-Clause
pypdf6.16.2BSD-3-Clausemerge, split, reorder, rotate, metadata, bookmarks, watermark overlay, encrypt/decrypt
pypdfium25.13.0BSD-3-Clause, Apache-2.0, dependency licensesPDF → images, page rendering, fast plain-text extraction
pyphen0.18.1GNU General Public License v2 or later (GPLv2+); GNU Lesser General Public License v2 or later (LGPLv2+); Mozilla Public License 1.1 (MPL 1.1)
pytesseract0.3.13Apache Software LicenseOCR driver for the tesseract binary
reportlab5.0.1BSD Licensecreate PDFs from scratch, images → PDF (lossless DCTDecode embed)
rich15.0.0MIT License
shellingham1.5.4ISC License (ISCL)
tinycss21.5.1BSD License
tinyhtml52.1.0MIT License
typer0.27.2MITCLI surface
weasyprint69.0BSD Licenseoptional HTML/Markdown → PDF ([html] extra, Phase 2)
webencodings0.6.1BSD License
zopfli0.4.3Apache Software License

Two system binaries are detected at runtime, never bundled:tesseract (Apache-2.0) and soffice / LibreOffice (MPL-2.0).