Compress on export: offer the setting that actually shrinks a scan - #159
Merged
Merged
Conversation
Compressing a 45 MB scan in Universal Compress got it to 1.5 MB; the same file in Universal PDF's Export dialog reported no savings and greyed the Compressed tab out. The engines are not the difference — Universal Compress's PDF path is a port of this one, down to the raster presets — the entry point was. Export called compressPdf with no quality, taking the 'light' default: a lossless object-stream re-save. On a scan, whose bulk is image data, that is a few percent at best, so the tab it feeds had nothing to offer. Only the landing page's compress pill ever passed 'balanced'. So give Export the same three-way control the Compress dialogs have. 'light' stays the default there and only there: an export is an annotated, often signed document, and rasterising it costs the text layer — a trade worth offering, not worth making on someone's behalf. It is spelled out in the dialog when a rasterising level is picked. Compression is now its own pass, so changing the level re-compresses the annotated bytes instead of re-baking every annotation to arrive at the same input again. Two things behind that, both of which bit the same big file: The never-return-a-bigger-file guarantee was measured by saving the whole source document a second time as a lossless yardstick. On a 45 MB scan that is a second 45 MB buffer on top of the source, pdf.js's copy and the parsed document, and it is the slowest step in the function. It is now only paid for when the lossless pass could plausibly win. Repacking removes structural slack, never image data, so a raster result under half the original is a landslide the yardstick cannot overturn. The guarantee is unchanged; a text-only PDF still comes back losslessly rather than as 860 KB of JPEG. And compressPdf reported no progress at all, so a long rasterising run looked like a hung dialog — which is how people learn to kill the tab mid-export. It now takes an onProgress callback, threaded through the export dialog as a bar and through the landing page and both Compress dialogs as a percentage. Verified in a browser against an 8-page image-heavy fixture: 16.72 MB → 16.72 MB at Light, → 1.41 MB at Balanced, → 0.29 MB at Maximum, with the text-only fixture still falling back to lossless.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A 45 MB scan compressed to 1.5 MB in Universal Compress but reported no savings in Universal PDF's Export dialog, which greyed the Compressed tab out entirely.
The engines are not the difference. Universal Compress's
src/lib/compress/pdf.tsis a port of this repo'scompressPdf, down to the raster presets (balanced1.5×/0.7,maximum1.0×/0.45) and the samepdf-lib/pdfjs-distversions. The entry point was.What was wrong
ExportModalcalledcompressPdfwith no quality argument, so it took the'light'default — a lossless object-stream re-save. On a scan, whose bulk is image data, that is a few percent at best, so the tab it feeds had nothing to offer and disabled itself. Only the landing page's compress pill ever passed'balanced'.Changes
Export gets the quality control. The same Light / Balanced / Maximum choice the Compress dialogs have.
'light'stays the default there and only there: an export is an annotated — often signed — document, and rasterising it costs the text layer. That is a trade worth offering, not worth making on someone's behalf, and the dialog says so plainly when a rasterising level is picked. Compression is now its own pass, so changing the level re-compresses the annotated bytes instead of re-baking every annotation and re-rasterising every redaction to arrive at the identical input again.The lossless yardstick is no longer always paid for. The never-return-a-bigger-file guarantee was measured by saving the whole source document a second time. On a 45 MB scan that is a second 45 MB buffer on top of the source, pdf.js's copy and the parsed document — the slowest step in the function, and one big files were dying on. It now runs only when the lossless pass could plausibly win: repacking removes structural slack, never image data, so a raster result under half the original is a landslide the yardstick cannot overturn. The guarantee itself is unchanged — a text-only PDF still comes back losslessly rather than as 860 KB of JPEG.
Progress reporting.
compressPdfreported nothing at all, so a long rasterising run was indistinguishable from a hung dialog. It now takes anonProgresscallback, threaded through the export dialog as a bar and through the landing page and both Compress dialogs as a percentage.Verification
Driven in a real Chromium against the dev server, not only typechecked.
Engine, on an 8-page image-heavy fixture:
Progress fires per page, is monotonic, and reaches 1 on both the lossless and rasterising paths.
The Export dialog itself, driven through the UI: opens on Light; at Light the scan shows "no savings" with the Compressed tab disabled (the reported bug); switching to Balanced shows a progress bar and then −92% with the tab live; the text-layer warning is visible.
Unrelated, but worth knowing for anyone doing browser-level testing here:
npm run devthrows into the ErrorBoundary withoutVITE_PLATFORM_SUPABASE_URL/VITE_PLATFORM_SUPABASE_ANON_KEYset. Pre-existing, and no env config is committed here.Generated by Claude Code