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
- Why Compress PDFs?
- PDF Compression Fundamentals
- 2.1 Lossless vs. Lossy Compression
- 2.2 Compression Targets: Images, Fonts, Objects
- 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
- Setting Up Your Environment
- Compressing with pikepdf (Lossless)
- 5.1 Installation
- 5.2 Basic Compression
- 5.3 Advanced Options: Object Streams & Linearization
- Compressing with Ghostscript (Lossy & Lossless)
- 6.1 Installing Ghostscript
- 6.2 Python Wrapper via subprocess
- 6.3 Tuning Quality vs. Size
- Compressing with qpdf (Linearization & Stream Optimization)
- 7.1 qpdf Overview
- 7.2 Python Integration
- Recompressing Images Manually
- 8.1 Extracting Images
- 8.2 Downsampling & Re-encoding (PIL)
- Building a CLI Tool for PDF Compression
- 9.1 Project Structure
- 9.2 Argparse & Subcommands
- Best Practices & Performance Tips
- Troubleshooting & Common Pitfalls
- 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
- Images
- Downsampling/resampling (reducing DPI).
- Re-encoding (e.g., converting from lossless TIFF to JPEG).
- Fonts
- Subsetting: embedding only the glyphs actually used.
- 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:
- Benchmark: Run your PDFs through different approaches and measure size vs. visual fidelity.
- Automate: Integrate compression into your CI/CD pipelines or document upload backends.
- 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!