
If you ever write a "shrink this PDF" step, the first number you will reach for is the image DPI. I built a test file specifically to break that number: a 1200×3000 px photo squashed into a 20 cm × 5 cm box. Measured along the width it is 152 dpi. Measured along the height it is 1524 dpi. Ask "is this image over-resolved?" and there is no single correct answer.
This post is a set of byte-level measurements on three self-made PDFs, run through four compression presets, to answer two questions: what is a PDF compressor actually removing, and why does a text-only PDF barely shrink no matter which preset you pick.
Setup
All samples are fictional documents I generated with reportlab:
- Manual: 8 pages, 19,458,294 bytes. Each page has one photo, one UI screenshot, Chinese body text and a vector bar chart. The photos come from a CC0 image on Wikimedia Commons (shot on an iPhone 6, not mine), cropped, recolored and upscaled to 4032×3024.
- Text-only: 10 pages of Chinese prose, 137,782 bytes, no images.
- Stretched: the single squashed photo above.
The compressor is the PDF tool in ImgIng (https://imging.ai/), which has four presets: Screen 72 dpi, eBook 150 dpi, Print 300 dpi, and lossless structure-only. On scope: I work on the on-device codec and model-loading parts of that product. I did not build the PDF engine, so everything below about its decisions comes from inspecting the output files and the per-image decision line the UI prints, not from reading its source. Processing runs in the browser; the Network tab showed zero non-GET requests while compressing and saving.
Where the bytes are
Counting encoded (not decompressed) stream bytes by type:
- Manual: images 19,308,166 bytes (99.23%), fonts 128,406 (0.66%), content streams 9,662 (0.05%).
- Text-only: fonts 117,421 (85.22%), content streams 11,847 (8.60%), the rest is dictionaries and xref.
Byte share per stream type in two self-made fictional samples. Right side: eBook preset result, manual down to 4.4%, text-only saves 4.8%.
All the text and the vector chart on 8 pages fit in under 10 KB of drawing operators. In these samples the size lives in embedded images first and fonts second; the text itself is nearly free.
Effective DPI, per axis
Effective DPI is pixels divided by displayed inches, and the displayed size comes from where the image is placed on the page. A quick way to see it with PyMuPDF:
for img in page.get_images(full=True):
xref, w_px, h_px = img[0], img[2], img[3]
for rect in page.get_image_rects(xref): # one image can be placed many times
dpi_x = w_px / (rect.width / 72) # PDF user space: 72 pt per inch
dpi_y = h_px / (rect.height / 72)
print(f"p{page.number+1} xref={xref} {w_px}x{h_px}px -> {dpi_x:.0f} x {dpi_y:.0f} dpi")
Using the bounding rect is fine for unrotated placements; for rotated ones you want the lengths of the transform's column vectors instead. On the manual it prints 1138 × 1138 dpi for the photos and 430 × 430 for the screenshots. On the stretched sample: 1200x3000px -> 152 x 1524 dpi.
After the eBook preset, the same script on the output prints 1200x295px -> 152 x 150 dpi. The width was left alone, only the height was resampled to 150. The UI decision line says the same thing: 152×1524 dpi · 1200×295. A single-axis check would have either thrown away horizontal detail for nothing or kept a 10× vertical excess.
Thresholds and codec choice, inferred from outputs
The photos in the manual land exactly on the preset: 255×191 at Screen, 531×399 at eBook, 1063×797 at Print. The 430 dpi screenshots are different. At Print (300 dpi) they were kept untouched at 2880×1800, and the decision line (I ran the Chinese UI) reads 原图已足够紧凑, "original is already compact". From the handful of data points I have, resampling seems to trigger only when the image exceeds roughly 1.5× the preset (450 dpi for Print), about 1.2× for Screen. I did not sweep a continuous DPI range, so treat those multipliers as observations, not a spec.
Codec choice also splits by content. Photos go to JPEG (q88 at eBook). Screenshots go to a 256-color palette with Flate, e.g. 227 KB → 33 KB · −85%. For flat UI with sharp text edges that avoids JPEG ringing around glyphs.
For contrast, the macOS Quartz filter "Reduce File Size" (the one you can pick when exporting from Preview; I invoked the same filter file from code and did not verify through the Preview UI) resamples everything to 144 dpi JPEG, screenshots included. On the manual that gave 1,432,231 bytes and a 150 dpi render PSNR of 38.00, against 864,614 bytes and 41.29 for the eBook preset.
What's left when images hit the floor
Screen preset takes the manual to 352,318 bytes. Re-counting streams: images 203,603, fonts 127,821. Fonts are now 36% of the file, because they barely change across presets while images shrink by two orders of magnitude. Push further and the only thing left to trade is legibility, which is already gone at Screen:
Small table text inside a screenshot of the self-made manual (Chinese content), 150 dpi render crop. Screen 344 KB is unreadable; eBook 844 KB and Print 3.55 MB are readable. Sizes as shown in the UI (1 MB = 1024 KB).
The text-only file
All four presets produced byte-identical output: 131,156 bytes, 4.8% smaller. The UI reports 10 unused resources removed and 12 streams recompressed or merged. There are no images, so the preset has nothing to act on. The font streams went from 117,421 to 116,861 bytes; decompressed, the font programs are identical before and after. The tool states that it does not subset fonts, and the output agrees.
One caveat I missed at first: reportlab had already embedded subset fonts (names carry an AAAAAA+ prefix). So this sample barely shrinks mostly because there was little redundancy to begin with. A PDF that embeds a full CJK font could behave very differently, and I haven't tested one, so I'm not drawing a general "text PDFs can't be compressed" conclusion from a single file. Side note: the macOS filter made this file 3.35% larger (142,400 bytes).
What I check now
Count stream bytes first. If images dominate and their per-axis effective DPI is far above the intended use, the eBook preset is a good default: the manual kept all 8 pages, text extracted identically, PSNR around 41. If fonts dominate, presets won't help; fix it where the PDF is generated. And if you're fitting under an upload limit, remember the UI uses 1024-based units (19,458,294 bytes shows as 18.56 MB), so check both 1000 and 1024 interpretations of the limit.

Top comments (0)