Government portals, job applications and university forms love one sentence: "File must be under 1 MB." The usual fix is an online compressor, which means uploading your ID, contract or bank statement to someone else's server.
I wanted the same result without the upload, so I built the compressor in CLEKARA to run entirely in the browser. This post is about how it hits a size target without wrecking the document, and the bugs I hit on the way.
The engine: Ghostscript compiled to WebAssembly
The heavy lifting is Ghostscript 9.56 compiled to WebAssembly, running in a module Web Worker so the page never freezes. A few practical notes:
-
gs.wasmis about 16 MB. Loading it on page start hurt LCP for everyone, including people who only came to read, so now it starts either 4 seconds after the page has loaded and the browser is idle, or as soon as the user interacts, whichever comes first. The UI shows an "engine loading / engine ready" badge instead of pretending it's instant. - Everything stays in memory: the PDF goes from a
Fileto anArrayBuffer, into the worker's virtual filesystem, and back out as aBlobyou download. Nothing is sent over the network.
Four profiles instead of one magic setting
Compression is mostly about images, so each profile is a set of Ghostscript image settings:
| Profile | Color/gray DPI | JPEG quality (QFactor) |
|---|---|---|
| High quality | 300 | 0.25 |
| Balanced | 200 | 0.50 |
| Strong | 150 | 0.76 |
| Maximum | 110 | 0.90 |
Text and vector graphics are never rasterized, so they stay sharp at every level.
"Smart" mode: probe, measure, then choose
Running all four profiles on a 200-page PDF would take forever, so Smart mode works on a sample:
- Pick 3 representative pages spread across the document.
- Make a baseline: rewrite only those pages with near-lossless settings, so every probe is compared against the same reference size.
- Compress the sample with each profile and record the size ratio against the baseline.
- Measure visual quality: render the original page and the compressed page to grayscale at 620 px and compute SSIM between them. I keep the worst score of the sampled pages.
-
Choose:
- throw away any profile that saves less than 5%;
- throw away any profile below a quality floor that depends on the document type: 0.97 for text-heavy PDFs, 0.95 for mixed, 0.93 for scans;
- from what's left, only move to a more aggressive profile if it saves at least 10 more percentage points. Otherwise you lose quality for very little.
If the user gave a target (say 1 MB), the rule changes to: the gentlest profile that fits the target.
One detail that saved me from silent nonsense: before computing SSIM I check that the probe really contains the same pages, in the same order, as the sample. If page i of the probe isn't page i of the original, the score is meaningless, so it's discarded instead of trusted.
When even "Maximum" isn't enough
Some files simply can't reach 200 KB at 110 DPI. For those, and only when the user set a target, there is a ladder that steps image resolution down to 80 → 60 → 45 DPI, validates each result, stops as soon as the file fits, and tells the user exactly how far it had to go. If it still doesn't fit, it says so and hands over the smallest valid file instead of pretending.
Bugs that taught me something
1. QFactor 1.0 is not "worst quality". In this Ghostscript build a QFactor of exactly 1.0 behaved like 0.5, so the "strongest" setting came out bigger than a weaker one. No profile uses 1.0 now.
2. "Maximum" was sometimes bigger than "Strong". With automatic image filters, Ghostscript occasionally decided some photos were better stored losslessly. Forcing JPEG on the Maximum profile fixed it.
3. My first target ladder wasn't monotonic. The first step (96 DPI) sometimes produced a larger file than Maximum at 110 DPI. Switching to 80/60/45 and removing an early-stop shortcut made the sizes decrease every step, which is the only behavior a user expects from "try harder".
Result
On a 3-page scanned-style test file, a 5.3 MB PDF with a 1 MB target came out at 854 KB (84% smaller) using the Strong profile, readable, in about 13 seconds in my test run, without the file ever leaving the tab.
You can try it here: Compress a PDF to 1 MB. It's free, with no account and no watermark, and there are 55 other PDF and image tools on the same site built the same way.
I'd love feedback from people who know PDFs better than I do: what kind of file would you throw at this to break it?
Top comments (0)