Python PDF Compression

Python PDF Compression

In an increasingly digital world, PDFs have become the de facto standard for document sharing and archiving. However, high-resolution images, embedded fonts, and complex vector graphics can bloat file sizes, leading to slow transfers, storage concerns, and even upload limits on various platforms. In this long-form guide, we’ll explore why PDF compression matters, discuss different compression techniques, and dive deep into Python-based solutions—complete with hands-on code examples—to help you reduce PDF sizes efficiently while preserving acceptable quality.

Table of Contents

  1. Why Compress PDFs?
  2. PDF Compression Fundamentals
    • 2.1 Lossless vs. Lossy Compression
    • 2.2 Compression Targets: Images, Fonts, Objects
  3. Choosing a Python Library or Tool
    • 3.1 pikepdf
    • 3.2 PyPDF2 / PyPDF4
    • 3.3 Ghostscript via subprocess
    • 3.4 qpdf
    • 3.5 Custom Image Processing with PIL / Pillow
  4. Setting Up Your Environment
  5. Compressing with pikepdf (Lossless)
    • 5.1 Installation
    • 5.2 Basic Compression
    • 5.3 Advanced Options: Object Streams & Linearization
  6. Compressing with Ghostscript (Lossy & Lossless)
    • 6.1 Installing Ghostscript
    • 6.2 Python Wrapper via subprocess
    • 6.3 Tuning Quality vs. Size
  7. Compressing with qpdf (Linearization & Stream Optimization)
    • 7.1 qpdf Overview
    • 7.2 Python Integration
  8. Recompressing Images Manually
    • 8.1 Extracting Images
    • 8.2 Downsampling & Re-encoding (PIL)
  9. Building a CLI Tool for PDF Compression
    • 9.1 Project Structure
    • 9.2 Argparse & Subcommands
  10. Best Practices & Performance Tips
  11. Troubleshooting & Common Pitfalls
  12. Conclusion & Next Steps

1. Why Compress PDFs?

  • Storage Efficiency: Smaller files consume less disk space, especially critical for archival.
  • Faster Transfers: Reduced size leads to quicker email attachments, uploads, and downloads.
  • Bandwidth Savings: On mobile or metered connections, compression minimizes data usage.
  • Compatibility: Many web platforms enforce file-size limits; compression ensures compliance.

Whether you’re an automated report generator, a document-heavy web service, or simply managing a library of user-uploaded PDFs, having a reliable compression workflow is indispensable.

2. PDF Compression Fundamentals

2.1 Lossless vs. Lossy Compression

  • Lossless: Preserves the exact original data. Ideal for text-heavy documents and legal contracts where fidelity is paramount. Techniques include stream dictionary optimization, object stream merging, and deflate compression of text streams.
  • Lossy: Achieves higher compression by discarding some data—primarily within images. Suitable for brochures, flyers, or documents where slight quality degradation is acceptable.

2.2 Compression Targets: Images, Fonts, Objects

  1. Images
    • Downsampling/resampling (reducing DPI).
    • Re-encoding (e.g., converting from lossless TIFF to JPEG).
  2. Fonts
    • Subsetting: embedding only the glyphs actually used.
  3. PDF Objects & Streams
    • Merging small streams into larger object streams.
    • Removing unused objects (via linearization or cleanup).

3. Choosing a Python Library or Tool

Python’s rich ecosystem offers multiple options:

3.1 pikepdf

A Pythonic wrapper around QPDF’s C++ library; excellent for lossless compression, repairing malformed PDFs, and fine-grained stream control.

3.2 PyPDF2 / PyPDF4

Pure-Python libraries for basic PDF manipulation. Limited compression capabilities but easy to install. Primarily used for merging, splitting, and metadata editing.

3.3 Ghostscript via subprocess

Ghostscript is a powerful interpreter for PostScript and PDF that can compress PDFs in both lossless and lossy ways. We invoke it from Python for robust results.

3.4 qpdf

A command-line toolkit offering linearization (“fast web view”), object stream creation, and incremental updates removal. Often used under the hood by pikepdf.

3.5 Custom Image Processing with PIL / Pillow

For ultimate control over images—extract, downsample, re-encode, and re-integrate.

4. Setting Up Your Environment

Ensure you have Python 3.7+ installed.

# Create virtual environment
python -m venv pdf_compress_env
source pdf_compress_env/bin/activate

# Core libraries
pip install pikepdf pillow
# Optional: for pure-Python fallback
pip install PyPDF4

For Ghostscript and qpdf, install via your OS package manager:

# Ubuntu / Debian
sudo apt-get update
sudo apt-get install ghostscript qpdf

5. Compressing with pikepdf (Lossless)

5.1 Installation

pip install pikepdf

5.2 Basic Compression

import pikepdf

def compress_pdf(input_path: str, output_path: str, optimize_streams: bool = True):
    """
    Compress PDF using pikepdf:
      - optimize_streams: merges object streams for better compression.
    """
    with pikepdf.open(input_path) as pdf:
        # Optimize content streams
        if optimize_streams:
            pdf.optimize(
                object_streams=True,    # merge small objects
                compress_streams=True   # gzip-compress streams
            )
        pdf.save(output_path)

if __name__ == "__main__":
    compress_pdf("original.pdf", "compressed.pdf")

What it does:

  • Merges object streams (object_streams=True).
  • Applies zlib (deflate) compression to existing streams (compress_streams=True).

5.3 Advanced Options: Object Streams & Linearization

pdf.optimize(
    object_streams=True,
    compress_streams=True,
    remove_unreferenced_objects=True,  # cleans up orphaned objects
    linearize=True                      # prepares for fast web viewing
)
  • remove_unreferenced_objects: Strips any stray data.
  • linearize: Moves cross-reference table to front for progressive loading.

6. Compressing with Ghostscript (Lossy & Lossless)

Ghostscript’s PDF writer offers ‘−dPDFSETTINGS‘`-dPDFSETTINGS` presets:

  • /screen: 72 dpi, low quality (smallest size).
  • /ebook: 150 dpi, moderate quality.
  • /printer: 300 dpi, high quality.
  • /prepress: 300 dpi, color preserving (larger size).
  • /default: nearly lossless, retains nearly all info.

6.1 Installing Ghostscript

# Ubuntu
sudo apt-get install ghostscript

6.2 Python Wrapper via subprocess

import subprocess

def compress_with_ghostscript(input_pdf: str, output_pdf: str, quality: str = "/ebook"):
    """
    Compress using Ghostscript:
      - quality: one of /screen, /ebook, /printer, /prepress, /default
    """
    gs_cmd = [
        "gs",
        "-sDEVICE=pdfwrite",
        f"-dPDFSETTINGS={quality}",
        "-dNOPAUSE",
        "-dBATCH",
        "-dQUIET",
        f"-sOutputFile={output_pdf}",
        input_pdf
    ]
    subprocess.run(gs_cmd, check=True)

if __name__ == "__main__":
    compress_with_ghostscript("original.pdf", "gs_compressed.pdf", quality="/screen")

6.3 Tuning Quality vs. Size

Experiment with custom DPI and downsampling parameters:

# Command-line example for 150 dpi color images:
gs \
  -sDEVICE=pdfwrite \
  -dColorImageDownsampleType=/Bicubic \
  -dColorImageResolution=150 \
  -dGrayImageDownsampleType=/Bicubic \
  -dGrayImageResolution=150 \
  -dMonoImageDownsampleType=/Subsample \
  -dMonoImageResolution=150 \
  -dNOPAUSE -dBATCH \
  -sOutputFile=out.pdf in.pdf

7. Compressing with qpdf

7.1 qpdf Overview

Primarily used for:

  • Linearization (fast web view).
  • Stream optimization (object streams).
  • Removing incremental updates (legacy edits).

7.2 Python Integration

import subprocess

def compress_with_qpdf(input_pdf: str, output_pdf: str):
    """
    Use qpdf to optimize streams and linearize.
    """
    cmd = [
        "qpdf",
        "--linearize",
        "--object-streams=generate",
        input_pdf,
        output_pdf
    ]
    subprocess.run(cmd, check=True)

8. Recompressing Images Manually

When the above tools don’t yield sufficient reduction, extract and re-compress images by hand.

8.1 Extracting Images

Use pdfimages (from Poppler) to dump images:

pdfimages -all original.pdf img_prefix

That generates img_prefix-0001.jpg, .png, etc.

8.2 Downsampling & Re-encoding (PIL)

from PIL import Image
import os

def recompress_image(input_path: str, output_path: str, max_width=1024, quality=70):
    img = Image.open(input_path)
    # Downsample if too wide
    if img.width > max_width:
        ratio = max_width / float(img.width)
        new_size = (max_width, int(img.height * ratio))
        img = img.resize(new_size, Image.LANCZOS)
    # Save as JPEG
    img.convert("RGB").save(output_path, "JPEG", quality=quality)

# Batch process
for fname in os.listdir("images"):
    recompress_image(f"images/{fname}", f"compressed/{fname}", max_width=1200, quality=65)

Then reassemble images back into the PDF using reportlab or pikepdf:

import pikepdf

def replace_images(pdf_path: str, image_dir: str, out_path: str):
    pdf = pikepdf.open(pdf_path)
    for page in pdf.pages:
        resources = page.Resources
        if "/XObject" in resources:
            xobj = resources["/XObject"]
            for name, obj in list(xobj.items()):
                if obj.Type == "/XObject" and obj.Subtype == "/Image":
                    img_name = obj.Name[1:]  # strip leading slash
                    if os.path.exists(f"{image_dir}/{img_name}.jpg"):
                        with open(f"{image_dir}/{img_name}.jpg", "rb") as f:
                            data = f.read()
                        new_obj = pikepdf.Stream(pdf, data)
                        # set appropriate filters / params here
                        xobj[name] = new_obj
    pdf.save(out_path)

9. Building a CLI Tool for PDF Compression

Want to expose all these methods behind a user-friendly CLI? Here’s an outline.

9.1 Project Structure

pdfcompress/
├── compress.py        # core functions
├── cli.py             # entry point with argparse
├── requirements.txt
└── README.md

9.2 Argparse & Subcommands

# cli.py
import argparse
from compress import compress_with_ghostscript, compress_pdf, compress_with_qpdf

def main():
    parser = argparse.ArgumentParser(
        description="PDF Compression CLI – supports Ghostscript, pikepdf, and qpdf"
    )
    subparsers = parser.add_subparsers(dest="cmd", required=True)

    # Ghostscript
    gs = subparsers.add_parser("gs", help="Compress via Ghostscript")
    gs.add_argument("-i", "--input", required=True)
    gs.add_argument("-o", "--output", required=True)
    gs.add_argument("-q", "--quality", default="/ebook")

    # pikepdf
    pk = subparsers.add_parser("pike", help="Compress via pikepdf (lossless)")
    pk.add_argument("-i", "--input", required=True)
    pk.add_argument("-o", "--output", required=True)

    # qpdf
    qp = subparsers.add_parser("qpdf", help="Optimize via qpdf")
    qp.add_argument("-i", "--input", required=True)
    qp.add_argument("-o", "--output", required=True)

    args = parser.parse_args()
    if args.cmd == "gs":
        compress_with_ghostscript(args.input, args.output, args.quality)
    elif args.cmd == "pike":
        compress_pdf(args.input, args.output)
    elif args.cmd == "qpdf":
        compress_with_qpdf(args.input, args.output)

if __name__ == "__main__":
    main()

10. Best Practices & Performance Tips

  • Batch Processing: Parallelize multiple PDFs via multiprocessing to leverage multi-core CPUs.
  • Pipeline Strategy: Chain tools (e.g., pikepdf → Ghostscript → qpdf) for layered optimization.
  • Logging & Metrics: Track original vs. compressed sizes to evaluate ROI of each method.
  • Configuration Profiles: Store presets (quality levels, DPI) in JSON/yaml for reproducibility.

11. Troubleshooting & Common Pitfalls

  • Malformed PDF Errors: pikepdf can repair many issues; use pikepdf.open(input, allow_overwriting_input=True) to attempt fixing.
  • Image Quality Loss: Preview your settings on sample pages before batch-running on your entire corpus.
  • Missing Fonts: Ghostscript may rasterize vector text if fonts are absent—install necessary font packages.

12. Conclusion & Next Steps

Compressing PDFs effectively requires a blend of lossless and lossy techniques tailored to your documents’ content and your quality requirements. With Python’s ecosystem—pikepdf for lossless stream optimization, Ghostscript for image downsampling presets, qpdf for linearization, and Pillow for bespoke image handling—you have a comprehensive toolkit at your fingertips.

Next Steps:

  1. Benchmark: Run your PDFs through different approaches and measure size vs. visual fidelity.
  2. Automate: Integrate compression into your CI/CD pipelines or document upload backends.
  3. Enhance: Add features like watermark removal, OCR integration, or page cropping for further size savings.

By mastering these tools, you’ll ensure your PDF workflows are both efficient and reliable—delivering smaller files without sacrificing professional quality. Happy compressing!

#python compress pdf #pdf compression python #python pdf optimization #pikepdf tutorial #ghostscript pdf compression #qpdf optimization #Pillow pdf images #lossless pdf compression #lossy pdf compression #reduce pdf size

Subscribe to our newsletter

12k+

Subscribers

Weekly

Frequency

Free

Always