Skip to content

feat(ui): choose quality before reducing PDF size (#2148) - #2899

Open
robertobulltech wants to merge 2 commits into
storytold:mainfrom
robertobulltech:feat/2148-reduce-quality
Open

robertobulltech wants to merge 2 commits into
storytold:mainfrom
robertobulltech:feat/2148-reduce-quality

Conversation

@robertobulltech

@robertobulltech robertobulltech commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Main integration — 2026-10-17

Integrated upstream main at 924c4da4 in 1ab3ed08, preserving the existing feature scope and branch history.

Local validation passed: formatting; strict all-target Clippy on affected crates; relevant library/integration tests; layering, asset attribution and parity gates; CLI without default features; dependency licence/advisory checks; and all 31 library crates on WASM with the pinned craft-fonts input. Native builds/tests use Rust 1.99, the low-disk profile, two build jobs and isolated Cargo targets. Existing ignored hardware/measurement probes remain explicit in the logs.

Full-workspace Linux/Windows CI for this exact commit: https://github.com/robertobulltech/pdfcraft/actions/runs/39019919003. The run is still in progress at publication; workflow dispatch also starts the additional nightly mutation-fuzz job.

The earlier validation history and feature limitations below remain for context.


What this changes

Refs #2148.

Reduce File Size currently starts immediately with one fixed quality, which can turn lossless technical drawings into JPEG. It now opens a small chooser with a description, the quality/size tradeoff, Reduce and Cancel. Only a confirmed choice is persisted; cancelling starts no worker and leaves the saved preference unchanged. Missing or invalid preferences fall back to Screen / email.

Quality Image policy
Screen / email (fresh-install default) Exactly Settings::default(): 150 ppi above 225 ppi, JPEG quality 60.
Print Supported unmasked JPEGs: 300 ppi above 450 ppi, JPEG quality 85. Lossless graphics keep their encoding, resolution and samples. Masked JPEGs and unsupported formats stay unchanged.

Print adds the narrow Compression::JpegOnly policy. It skips lossless images rather than downsampling them and encoding the result as Flate: a lossless codec alone would not preserve their pixels. Print also disables fragment merging, which can resample mixed JPEG/Flate fragments. Images are still replaced only when their encoded result is smaller.

The existing background worker, progress card, cancellation, form-typing commit, snapshot of unsaved edits, reduced-copy naming, atomic writing, error reporting and refusal to save a non-beneficial reduction are reused. Standard/Mobile presets, global defaults and advanced optimizer settings keep their existing behavior. doc_reduce gains optional quality: screen|print; omitted quality preserves CLI/automation defaults.

Labels use the existing localization mechanism, with new translations in all 22 bundled catalogs. Catalog hashes, optimizer/automation documentation and the M11 roadmap/parity entries are updated. No dependencies or binary fixtures are added.

Baseline and reproduction

crates/optimize/tests/optimize.rs::reduce_quality_baseline generates contributor-original 1200×800 image fixtures: a textured photo-like JPEG, sharp Flate RGB line art with hatching, and a mixed page with normal searchable text, a valid link and a bookmark. These measurements were also reproduced with unmodified upstream optimizer sources at f0c9901c398372782a4eb001c95638289a5edab0:

Fixture Original bytes Existing Screen result
High-resolution JPEG 336,888 6,396
Flate drawing 190,333 19,593
Mixed document 526,517 25,307

The drawing becomes JPEG in the latter two results. Print produces a 214,261-byte mixed RGB document while retaining the drawing's exact decoded samples, including after save/reopen. These are fixture measurements, not output-size estimates. Engine/CLI saves may add a modification date, accounting for a small byte-count difference.

How you verified it

All Cargo commands used the installed Rust 1.99 toolchain, existing target cache, --locked --profile low-disk -j 2, and timeouts. No toolchain installation, workspace test sweep or WASM build.

  • cargo fmt --all --check and git diff --check
  • Strict cargo clippy -p pdfcraft-optimize -p pdfcraft-ui-egui -p pdfcraft-automation --all-targets -- -D warnings
  • cargo test -p pdfcraft-optimize: 26 tests; includes unchanged Screen settings, Print thresholds/quality, grayscale/RGB line-art encoding and exact pixels after save/reopen, text/page/link/bookmark preservation, masked-image fallback and JPEG sample tolerance.
  • UI create (13), commands (24), control (59), the form-typing chooser regression (1), optimize_ui::tests (3), and catalog/localization tests (115): confirmed-only persistence, invalid/missing preferences, cancel without a worker/output, concurrent requests, progress/cancellation, and failed/cancelled/non-beneficial results leaving original bytes/files untouched.
  • Automation reduc tests (3) and optimizing_through_tools (1): optional/default/invalid quality, Print dimensions through the tool, saved copies and unchanged source documents.
  • Engine an_optimize_job_reports_its_stages_and_can_be_cancelled (1).
  • The fidelity regression was observed failing with the Print preservation guard removed, then passing with it restored.
  • cargo xtask assets --write, assets, layers (37 crates), and parity.
  • Desktop/CLI build; real pdfcraft-cli run --script creates both reductions, reopens Print, extracts searchable text and renders the output. Render visually inspected.

Native KDE Plasma / Wayland

Launched the built desktop app with isolated XDG preferences, PDFCRAFT_SYSTEM_FONTS=0, no DISPLAY, and the opt-in control channel. Verified the fresh Screen selection, coordinate clicks switching to Print, updated description, Cancel without progress, non-beneficial completion without a copy, and Print restored after a normal quit/restart. Changing back to Screen then cancelling preserved Print. An 80-image synthetic PDF showed real worker progress; Cancel stopped it and the source SHA-256 remained unchanged. A synthetic signature field triggered the existing signed-document rewrite error. Native chooser, progress, completion and error screenshots were inspected locally.

Physical mouse input and the KDE native Save dialog were not exercised. Successful smaller-copy saving and the saved notice are covered by the UI worker tests; native completion was checked with a non-beneficial reduction.

Never-crash, provenance and security

  • No new panic shortcut outside tests, input-derived indexing/allocation, recursion, or unsafe code.
  • Generated contributor-original fixtures only; no third-party documents, Adobe assets, fonts or copied copyleft code. Translation attribution hashes checked; generated ATTRIBUTION.md remains unchanged.
  • No new network access. CLI root confinement and atomic writing use the existing paths. MCP/control remain opt-in and unchanged.
  • Headless reach and parity evidence updated; milestone estimates unchanged.

PR #1282 and known limitations

#1282 was inspected before implementation and rechecked before publication: it remains open and unmerged. This PR neither implements its lossless profile nor depends on its code. Keep image quality is deferred to that work; Print is not a lossless document mode. Masked JPEGs stay unchanged to avoid the shared-soft-mask resampling issue addressed there. Existing optimizer safety/codec limitations outside this conservative Print policy are unchanged.

No estimated output sizes, saved advanced presets, or broader optimizer/renderer refactor. Print can produce a larger copy than Screen, or no beneficial reduction. No competing PR implementing the #2148 chooser was found; #2650 concerns Combine Files and is separate. Related merged #1784 and #370 were reviewed for compatibility.

AI assistance: OpenAI Codex for @robertobulltech.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant