PDF300

Ghostscript PDF Compression: Presets, Commands, and Why the Output Is Sometimes Bigger

Updated · 4 min read · By

The situation

You have a server, a folder of PDFs, and no interest in clicking through a web form 200 times. Ghostscript has been the standard command-line PDF rewriter for decades, it is free software, and it is what many "online compressor" sites run behind the upload button.

It is also easy to misuse. The presets are coarse, some flags are counter-intuitive, and under certain conditions the "compressed" file is larger than the original.

Install

Ghostscript is licensed under the AGPL, with a separate commercial license from Artifex for products that cannot comply with the AGPL. If you are embedding it in something you sell, read the license first.

The basic command

gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 -dPDFSETTINGS=/ebook \
   -dNOPAUSE -dQUIET -dBATCH -sOutputFile=out.pdf in.pdf

-sDEVICE=pdfwrite selects the PDF output device. -dPDFSETTINGS picks a preset. -dNOPAUSE -dBATCH -dQUIET make it run non-interactively. -dCompatibilityLevel=1.5 or higher enables object streams, which shave a few percent off text-heavy files; 1.4 is the common default in old tutorials and leaves that saving on the table.

What the presets do

Preset Color/gray image resolution Intended use
/screen 72 dpi Screen viewing only; small print suffers
/ebook 150 dpi Reading on screen, general use — the usual starting point
/printer 300 dpi Office printing
/prepress 300 dpi, color preserved Print production; large output
/default Roughly /screen behavior with fewer assumptions Rarely what you want

Every preset downsamples images above its resolution, re-encodes color and gray images as JPEG (DCT), and subsets embedded fonts. Presets do not touch vector content or text, which is why a text-only PDF comes out about the same size under every preset.

Taking control of individual settings

Presets are bundles of distiller parameters; you can set the parameters yourself.

Downsampling resolution and method:

gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 \
   -dDownsampleColorImages=true -dColorImageResolution=120 -dColorImageDownsampleType=/Bicubic \
   -dDownsampleGrayImages=true  -dGrayImageResolution=120  -dGrayImageDownsampleType=/Bicubic \
   -dDownsampleMonoImages=true  -dMonoImageResolution=300  \
   -dColorImageDownsampleThreshold=1.0 -dGrayImageDownsampleThreshold=1.0 \
   -dNOPAUSE -dQUIET -dBATCH -sOutputFile=out.pdf in.pdf

The threshold matters: by default (1.5) an image is only downsampled if it is at least 1.5× the target resolution. Setting it to 1.0 downsamples everything above the target.

Convert to grayscale:

-sColorConversionStrategy=Gray -dProcessColorModel=/DeviceGray

JPEG quality (lower QFactor = higher quality; 0.4 is good, 0.76 is the /screen level, above 1.0 shows artifacts):

gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 -dNOPAUSE -dQUIET -dBATCH \
   -dAutoFilterColorImages=false -dColorImageFilter=/DCTEncode \
   -sOutputFile=out.pdf \
   -c "<< /ColorImageDict << /QFactor 0.6 /Blend 1 /HSamples [1 1 1 1] /VSamples [1 1 1 1] >> >> setdistillerparams" \
   -f in.pdf

(The -c … -f form passes PostScript before the input file; it is the documented way to set the JPEG dictionary. Keep all -d/-s switches before -c.)

Hitting a target size

Ghostscript has no "make it 9.5 MB" option. You loop:

#!/usr/bin/env bash
# usage: ./fit.sh in.pdf out.pdf 9500000   (target in bytes)
in=$1; out=$2; target=$3
for res in 200 150 120 100 72; do
  gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 -dNOPAUSE -dQUIET -dBATCH \
     -dDownsampleColorImages=true -dColorImageResolution=$res -dColorImageDownsampleThreshold=1.0 \
     -dDownsampleGrayImages=true  -dGrayImageResolution=$res  -dGrayImageDownsampleThreshold=1.0 \
     -sOutputFile="$out" "$in"
  size=$(stat -c%s "$out" 2>/dev/null || stat -f%z "$out")
  echo "res=$res size=$size"
  [ "$size" -le "$target" ] && exit 0
done
echo "target not reached at 72 dpi; split or convert to grayscale" >&2; exit 1

This is exactly the loop a target-size tool automates — with the difference that PDF300 estimates the result from sample pages first instead of re-running the whole file at every step, which matters on a 500 MB input.

Why the output is sometimes bigger

What pdfwrite changes besides size

pdfwrite is not an editor; it interprets the document and writes a new one. Expect:

Check the output before you replace the original. Rewrite on a copy.

FAQ

Is /screen always the smallest?

For image-heavy files, yes. For text-only files the presets produce nearly identical sizes, because text is not downsampled. If /screen did not help, the weight is in fonts, attachments or vector content.

Can Ghostscript hit an exact file size?

No. It applies the settings you give it and the size is whatever results. To hit a limit, loop downward as above or use a tool built around a target.

Does it keep the OCR text layer of a scanned PDF?

The invisible text layer is ordinary text objects with an invisible render mode, and pdfwrite preserves text. Confirm with a search (Ctrl+F) in the output, because some upstream OCR tools produce layers that do not round-trip cleanly.

Written by LeDeuxions, an architect who built PDF300 after a 300 MB portal limit kept rejecting drawing sets. Figures quoted from third parties link to their source; sizes and reductions described as "typical" are estimates, not guarantees.