Skip to main content

Convert PDF files to the archival PDF/A format

Project description

pdftopdfa

Python Version License

I built pdftopdfa as a free and open-source alternative to Ghostscript-based PDF/A converters. Ghostscript uses a dual license (AGPL/commercial) that makes it difficult to use in commercial products without purchasing a license. pdftopdfa is licensed under the permissive MPL-2.0 and can be freely used in commercial projects. Instead of re-rendering via Ghostscript, it modifies the PDF structure directly using pikepdf (based on QPDF), preserving the original content, fonts, and layout.

Highlights

  • No Ghostscript required -- direct PDF manipulation via pikepdf/QPDF
  • PDF/A-2b, 2u, 3b, 3u -- supports modern PDF/A levels (ISO 19005-2 and 19005-3)
  • Automatic font embedding -- uses policy-approved Windows system fonts or bundled replacements
  • Font subsetting -- reduces file size by removing unused glyphs
  • CJK support -- embeds Noto Sans CJK for Chinese, Japanese, and Korean text
  • ICC color profiles -- automatically embeds sRGB, CMYK, and grayscale profiles
  • Batch processing -- converts entire directories, optionally recursive
  • Integrated validation -- checks conformance via veraPDF
  • OCR support -- optional PP-OCRv6 Medium text recognition on the CPU, with explicit offline model directories and no runtime model downloads
  • Simple API -- usable as CLI tool or Python library

How It Works

pdftopdfa applies a multi-step conversion pipeline to make a PDF compliant with the PDF/A standard:

  1. Pre-check -- skips encrypted and digitally signed PDFs, then detects if the PDF is already a valid PDF/A file (skips conversion if the existing level meets or exceeds the target; optionally skips any veraPDF-compliant PDF/A via --skip-any-pdfa; see Usage Guide for details)
  2. OCR (optional) -- optionally orients pages with the bundled PaddleOCR orientation model, straightens skewed scans, and recognizes text with externally supplied PP-OCRv6 Medium models; OCRmyPDF rasterizes pages and creates the searchable text layer
  3. Font compliance -- analyzes all fonts, embeds missing ones, adds ToUnicode mappings, subsets embedded fonts, and fixes encoding issues
  4. Sanitization -- removes or fixes non-compliant elements (JavaScript, non-standard actions, transparency groups, annotations, optional content, etc.)
  5. Metadata -- synchronizes XMP metadata with the document info dictionary and sets the PDF/A conformance level
  6. Color profiles -- detects color spaces and embeds the required ICC profiles (sRGB, CMYK/FOGRA39, sGray)
  7. Save -- writes the output with the correct PDF version header

Installation

Prerequisites

  • Python 3.12, 3.13, or 3.14
  • macOS 14 or later on Apple Silicon, Linux, or Windows

Intel-based Macs are not supported. Installation on macOS requires the ARM64 wheels provided by ONNX Runtime for Apple Silicon.

pip install pdftopdfa

Optional: OCR support

pip install "pdftopdfa[ocr]"

OCR uses PaddleOCR 3.7 with ONNX Runtime on the CPU. It does not download models at runtime. Detection and recognition models must be obtained separately and passed explicitly on every OCR invocation.

PP-OCRv6 model setup

Download the files from these exact model revisions:

Each model directory must contain exactly inference.onnx and inference.yml. Missing files, extra files, symbolic links, unexpected sizes, or hash mismatches are rejected before PaddleOCR is initialized.

Model File Size (bytes) SHA-256
Detection inference.onnx 62,032,837 eb13b44b25bb36f89528b68720af8a61d9cf381176107f465db1757b65d086e1
Detection inference.yml 886 7298d5ead546584af2504d03355f881ac7a7bc0eb1e282d3e159277c1d0af871
Recognition inference.onnx 76,554,979 9c09abf0957f7968c7586464b7397b84ad2387a0497a351af40e9acc71b673ba
Recognition inference.yml 150,580 991b700facf5b50a7de193468207d5f4255b538dde0d312ae3b7c7a9b6873129

The models are not included in the source distribution or wheel. Keep them in deployment-managed, read-only directories. Both --ocr-detection-model-dir and --ocr-recognition-model-dir are required together; supplying the pair enables OCR without an additional --ocr flag. Conversely, --ocr, --ocr-force, --deskew, and --rotate-pages are rejected unless both model options are present.

--ocr-lang defaults to en. Use de for German and de+en for mixed German/English metadata. The accepted PaddleOCR 3.7 codes are:

af, az, bs, ca, ch, chinese_cht, cs, cy, da, de, en, es, et, eu, fi, fr, french, ga, german, gl, hr, hu, id, is, it, japan, ku, la, lb, lt, lv, mi, ms, mt, nl, no, oc, pl, pt, qu, rm, ro, rs_latin, sk, sl, sq, sv, sw, tl, tr, uz, vi.

Legacy codes such as eng and deu are not accepted. See the PaddleOCR language documentation for the language families represented by these codes.

Quick Start

# Simple conversion (creates document_pdfa.pdf)
pdftopdfa document.pdf

# Specific PDF/A level
pdftopdfa -l 2b document.pdf

# With validation (note: -v = --validate, not verbose; use --verbose for logs)
pdftopdfa -v document.pdf

# Skip any existing veraPDF-compliant PDF/A
pdftopdfa --skip-any-pdfa document.pdf

# Explicitly convert a signed PDF, invalidating its digital signatures
pdftopdfa --allow-signature-invalidation document.pdf

# Convert an entire directory
pdftopdfa -r ./documents/ ./output/

# The OCR examples below use the externally managed model directories
DET_MODEL=/opt/pdftopdfa/models/PP-OCRv6_medium_det_onnx
REC_MODEL=/opt/pdftopdfa/models/PP-OCRv6_medium_rec_onnx

# OCR a German/English scanned PDF
pdftopdfa --ocr-lang de+en \
  --ocr-detection-model-dir "$DET_MODEL" \
  --ocr-recognition-model-dir "$REC_MODEL" \
  document.pdf

# Automatically orient pages without deskewing them
pdftopdfa --rotate-pages \
  --ocr-detection-model-dir "$DET_MODEL" \
  --ocr-recognition-model-dir "$REC_MODEL" \
  document.pdf

# Deskew pages without changing their 90-degree orientation
pdftopdfa --deskew \
  --ocr-detection-model-dir "$DET_MODEL" \
  --ocr-recognition-model-dir "$REC_MODEL" \
  document.pdf

# Deskew and orient pages without converting the result to PDF/A
# (creates document_processed.pdf)
pdftopdfa --no-pdfa --deskew --rotate-pages \
  --ocr-detection-model-dir "$DET_MODEL" \
  --ocr-recognition-model-dir "$REC_MODEL" \
  document.pdf

# Preserve known proprietary stamps as PDF Stamp annotations
pdftopdfa --preserve-stamps document.pdf
from pathlib import Path
from pdftopdfa import convert_to_pdfa

result = convert_to_pdfa(
    input_path=Path("input.pdf"),
    output_path=Path("output.pdf"),
    level="2b",
)

ocr_result = convert_to_pdfa(
    input_path=Path("scan.pdf"),
    output_path=Path("scan_pdfa.pdf"),
    level="2b",
    ocr_languages=["de", "en"],
    ocr_detection_model_dir=Path(
        "/opt/pdftopdfa/models/PP-OCRv6_medium_det_onnx"
    ),
    ocr_recognition_model_dir=Path(
        "/opt/pdftopdfa/models/PP-OCRv6_medium_rec_onnx"
    ),
)

Supplying both model directories enables OCR in convert_to_pdfa(), convert_files(), and convert_directory(). Supplying only one directory, or requesting an OCR option without both directories, raises ValueError before processing starts.

Set pdfa=False to apply only the requested OCR processing. This skips font embedding, PDF/A sanitization, metadata synchronization, color-profile embedding, and PDF/A validation. The result is not validated or guaranteed to remain PDF/A compliant.

See docs/usage.md for the full CLI reference, Python API documentation, and examples.

Limitations

  • No PDF/A-1 support -- only PDF/A-2 and PDF/A-3 levels are supported
  • Encrypted PDFs -- password-protected PDFs cannot be converted
  • Digitally signed PDFs -- signed PDFs are copied unchanged by default because conversion would invalidate their signatures; use --allow-signature-invalidation only when an unsigned archival copy is intentional
  • Font replacement -- fonts without a suitable metrically compatible replacement produce a warning; the resulting file may not be fully compliant
  • Non-embedded CIDFonts (Identity encoding) -- content streams reference glyph IDs of the original font; after replacement with a substitute font the same glyph IDs point to different or missing glyphs, so the affected text may render incorrectly or invisibly. Text extraction and copy/paste stay correct because the original ToUnicode mapping is preserved. A warning is emitted for each replaced CIDFont

Font Sourcing

  • On Windows, pdftopdfa may automatically embed a conservative fixed allowlist of local fonts from %WINDIR%\Fonts.
  • A Windows system font is only used when the installed file lives under %WINDIR%\Fonts, its actual PostScript name is allowlisted, and its OpenType fsType permits outline embedding.
  • On macOS and Linux, system fonts are never auto-embedded; bundled replacement fonts are used instead.
  • fsType checks are a technical safeguard only and do not replace the font vendor's EULA or other license terms.
  • For auditable deployments, keep the allowlist tied to reviewed target systems or golden images.

Development

pip install -e ".[dev,ocr]"

Running Tests

pytest

The test suite contains 2600+ tests covering fonts, color profiles, metadata, sanitization, and end-to-end conversion.

Code Quality

ruff check src/ tests/   # Linting
ruff format src/ tests/  # Formatting

Documentation

Additional documentation is available in the docs/ folder:

Contributing

Contributions are welcome! Please open an issue to report bugs or suggest features, or submit a pull request.

Dependencies

Core:

  • pikepdf -- PDF manipulation (based on QPDF)
  • lxml -- XMP metadata processing
  • fonttools -- Font analysis, subsetting, and embedding
  • click -- CLI framework
  • colorama -- Colored terminal output
  • tqdm -- Progress bars
  • PaddleOCR -- document orientation and PP-OCRv6 text recognition
  • ONNX Runtime -- CPU inference for Paddle models

Optional:

  • OCRmyPDF -- PDF rasterization, text-layer generation, and page merging for optional OCR
  • pypdfium2 -- PDF page rasterizer for OCR
  • veraPDF -- ISO-compliant PDF/A validation

Acknowledgments

This project bundles the following resources:

License

This project is licensed under the Mozilla Public License 2.0 or later (MPL-2.0+) -- see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pdftopdfa-0.8.0.tar.gz (27.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pdftopdfa-0.8.0-py3-none-any.whl (27.0 MB view details)

Uploaded Python 3

File details

Details for the file pdftopdfa-0.8.0.tar.gz.

File metadata

  • Download URL: pdftopdfa-0.8.0.tar.gz
  • Upload date:
  • Size: 27.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pdftopdfa-0.8.0.tar.gz
Algorithm Hash digest
SHA256 a30fa403cf7c8c1e85014df9626811d6518a6c55b54f9590fe05499d4b6fd74b
MD5 78d9d9038aba93292761dec895b9a3e5
BLAKE2b-256 10b52fd4acff3d2052dd95cc3dc710683a122a78a19a6d16a0ccf478c0ca1263

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdftopdfa-0.8.0.tar.gz:

Publisher: publish.yml on iRedPaul/pdftopdfa

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdftopdfa-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: pdftopdfa-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 27.0 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pdftopdfa-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c61c13476a02003fccf5cd5fab42660e7cbf6779a345f68691b746006ba593a7
MD5 4261c1579802aa53a01687c034af7170
BLAKE2b-256 f0494e537750b9c1004e4e7e4691ea0d1365b451a95001a1cadbb6901de61882

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdftopdfa-0.8.0-py3-none-any.whl:

Publisher: publish.yml on iRedPaul/pdftopdfa

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page