DEV Community

Cover image for Why is my PDF 22 MB? A 5-minute diagnosis before you pick a compressor
Enlh NG
Enlh NG

Posted on Edited on

Why is my PDF 22 MB? A 5-minute diagnosis before you pick a compressor

Most "best PDF compressor" lists treat every PDF the same. But a compressor can only shrink what's actually in the file, and what's in a PDF varies a lot. Run the same tool on two files and you can get 95% off one and nothing off the other. That isn't a bad tool.

So before choosing a tool, spend five minutes finding out where the bytes are. This post is a short diagnosis routine, a script that does the counting, and a table that maps what you find to the fix that works. I checked the table on four test files I built.

Step 1: Where do the bytes go?

A PDF is mostly a few kinds of streams: images, fonts, and page content (the drawing instructions for text and vector graphics), plus a little structure. This script adds them up (PyMuPDF, pip install pymupdf):

import sys, collections, os, pymupdf

def report(path):
    doc, total = pymupdf.open(path), os.path.getsize(path)
    kinds = collections.Counter()
    for x in range(1, doc.xref_length()):
        try:
            if not doc.xref_is_stream(x): continue
            n = len(doc.xref_stream_raw(x))
        except Exception:
            continue
        keys = doc.xref_get_keys(x)
        sub = doc.xref_get_key(x, "Subtype")[1] if "Subtype" in keys else ""
        if sub == "/Image": kinds["images"] += n
        elif any(k.startswith("FontFile") for k in keys) or "Length1" in keys: kinds["fonts"] += n
        else: kinds["page content & other"] += n
    kinds["structure"] = max(total - sum(kinds.values()), 0)
    print(f"{path}: {total/1048576:.2f} MB, {len(doc)} pages")
    for k, v in kinds.most_common():
        print(f"  {k:22s} {v/1048576:7.2f} MB  {100*v/total:5.1f}%")
    dpis, seen = [], set()                      # effective resolution of placed images
    for page in doc:
        for i in page.get_image_info(xrefs=True):
            w_in = (i["bbox"][2] - i["bbox"][0]) / 72
            if i["xref"] and i["xref"] not in seen and w_in > 0:
                seen.add(i["xref"]); dpis.append(i["width"] / w_in)
    if dpis: print(f"  images: {len(dpis)}, effective {min(dpis):.0f}-{max(dpis):.0f} dpi")

report(sys.argv[1])
Enter fullscreen mode Exit fullscreen mode

"Effective dpi" is the image's pixel width divided by the width it's drawn at on the page. A page image at 300 dpi or more is great for printing and wasted on a screen or an email.

The four files I measured

I generated four PDFs with known contents (script at the end), then ran the report:

File Report says Why it's that way
A scanned pages (5 pages, 7.7 MB) images 99.9%, all at 300 dpi Each page is one JPEG photograph of a page
B photo brochure (6 pages, 22.7 MB) images 100%, 349–524 dpi Big photos placed small, some lossless (Flate), some JPEG
C plain text (40 pages, 38 KB) page content 62%, structure 38%, no images Text drawn with a standard font
D same text, streams not compressed (338 KB) page content 96% Same file as C, saved without compression

(My test files use a standard font, so none of them has an embedded font. The script reports fonts if yours does.)

Step 2: Try the levers, and see what each does to each file

I applied three levers to each file:

  1. Lossless rewrite: recompress streams and pack objects (pikepdf.save(...) with object streams). Changes nothing you can see.
  2. Images to 150 dpi, JPEG quality 75: PyMuPDF rewrite_images.
  3. Images to 96 dpi, JPEG quality 50: the same call, more aggressive.

Size after each lever, as % of the original

File 1 lossless 2: 150 dpi, q75 3: 96 dpi, q50
A scanned (7.7 MB) 100% 68.6% 12.6%
B photo brochure (22.7 MB) 83.1% 4.6% 2.2%
C text, compressed (38 KB) 60.5% 78.5%* 78.5%*
D text, uncompressed (338 KB) 6.8% 8.9%* 8.9%*

* There are no images in C and D, so levers 2 and 3 do nothing for them. Those numbers differ from lever 1 only because PyMuPDF writes the file's structure differently from pikepdf. Both are lossless; it's a different saver, not a different compression level.

What the table says:

  • Scanned pages (A): lossless did nothing (100.0%, the pages are already JPEG). The only lever that works is lowering the image resolution and quality.
  • Photo-heavy (B): a lossless pass gets you 17% at most. Downsampling is where the gain is. Caveat: my photos are small sample images enlarged to 2400 px, so there was little real detail to lose, and the 95% drop is optimistic. A real camera photo shrinks less. The direction holds: images stored at 350–520 dpi for a document people read on a screen is where the waste is.
  • Text-only (C, D): nothing to downsample. The only gain is lossless: 40% off a file that was already compressed (C), and 93% off a file whose streams weren't compressed (D). If a text-only PDF is big, ask whether it's actually stored uncompressed or carries huge embedded fonts.

That's the reason one compressor claims "up to 90%" and another gets 15% on your file. They aren't measuring the same kind of file.

Step 3: What does the lossy lever cost?

Smaller is only useful if it's still readable. For page 1 of each file I compared a render before and after (SSIM, 1.0 = identical, at 100 dpi). For the scan I also OCR'd two pages with Tesseract (200 dpi render) and counted how many of the 68 tagged lines came back intact.

File Lever SSIM, page 1 OCR lines found (scan only)
A scanned original n/a 68 / 68
A scanned 150 dpi, q75 0.997 68 / 68
A scanned 96 dpi, q50 0.982 68 / 68
A scanned 72 dpi, q50 n/a 12 / 68
B brochure 150 dpi, q75 0.997 n/a
B brochure 96 dpi, q50 0.993 n/a

Down to 96 dpi the text was still fully recognised. At 72 dpi, it fell off a cliff: 12 of 68 lines. (I also tried 84 dpi, but the tool produced the same file as 96, so I can't say where between 72 and 96 the line is.) The text in my scan is clean, large (about 10.5 pt) and perfectly straight. Real scans with small print, skew or handwriting will break earlier. Treat 96 dpi as an upper bound for "safe", not a target.

SSIM stayed above 0.98 everywhere, but SSIM won't tell you whether a signature or a stamp is still readable. Look at the result at 100% zoom.

Step 4: Decision table

The report says Likely cause Do this
Images are 90%+ and effective dpi is 250+ Print-resolution images in a screen document Downsample to ~150 dpi (about 100–120 for email-only)
Images 90%+, all pages are one full-page image Scanned document Downsample, then keep OCR in mind; if you need searchable text, run OCR after
Images 90%+, dpi is already ≤150 Already compressed Expect little. Try JPEG quality, or split the file instead
Page content is the largest part, no images Vector art or uncompressed streams Lossless rewrite (can save a lot or almost nothing)
Fonts are a big share Fully embedded fonts Subset fonts (re-export from the source app); lossless alone won't remove them
Size doesn't change after "compression" Already optimal or the tool skipped the images Re-run the report on the output

Online tools: what each says about its own limits (read Nov 2026)

I used libraries above, not these sites, so I can't say what any of them do to your file. This is what each tool's page states. "Not stated" means I didn't find it, not that it doesn't exist.

Tool Limits stated Compression options stated Files / sign-up
Smallpdf "Usage limits may apply," no figures Basic, Moderate (Pro), Strong (Pro) Deleted after one hour; no account needed
iLovePDF Not stated Extreme, Recommended, Less Retention, sign-up not stated
Adobe Acrobat online Files up to 2 GB; "limited use" for free, no number High, Medium, Low; presets for screen, print, high-quality print No sign-up for basic compression; deleted unless you sign in to save
Sejda The page shows several: "3 tasks per hour," 100 MB or 200 pages, and also "50 MB" and "50 pages" messages, which don't agree Image quality, resolution (72 to 720 ppi), grayscale, optional font optimization Deleted after 2 hours; registration not required
PDF24 "No limits and no watermarks" DPI and image quality, lossless mode, 14 optional toggles Deleted after one hour; processed on its servers; no registration

Two observations. Tools that expose dpi and quality (Sejda, PDF24) let you act on the diagnosis above; tools with three named presets don't say what dpi they use, so check the result with the report script. And Sejda's page contradicts itself on its free limits, so test with your own file instead of relying on any one number.

I also run a free PDF compressor at ToolTiny. Its page states a 50 MB limit, three presets (Extreme, Recommended, Less), no account, and says password-protected PDFs must be unlocked first. As with the others, run the report on what comes out.

A 60-second version

  1. Run the script. Note images vs content vs fonts.
  2. Images dominate? Lower resolution first, then quality. Check readability at 100% zoom.
  3. Text only? Try a lossless rewrite. If that does nothing, the file is probably already compact.
  4. Scan with small print? Don't go below ~100 dpi without testing. OCR is the canary.
  5. Confidential file? Compress locally, or prefer a tool that states retention.
  6. Keep the original.

Limits of this post

  • Four synthetic files. Real PDFs mix everything, and add layers, forms, attachments and embedded fonts.
  • B's photos are enlarged samples, so its saving is overstated (said above).
  • One tool per lever (pikepdf for lossless, PyMuPDF for lossy). Ghostscript, mozjpeg-based tools and commercial engines can give different numbers.
  • The OCR test used one clean synthetic font at ~10.5 pt.
  • Vendor limits are as stated on their pages on the day I read them.

If you have a PDF where the report says one thing and compression behaves differently, tell me the report's numbers (not the file) in the comments. I'd like to add that case.

Top comments (0)