mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-13 14:22:25 +00:00
Optimize target_bytes: keep zip/retain image encoding on retries, only tighten ppi
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0b60a3b92
commit
8239f9c4ed
3 files changed
+16
-5
No files matched your search
@@ -93,6 +93,7 @@ const MAX_DPI: f64 = 600.0;
|
||||
/// How far a watermark may be moved from the page centre, in points (a page is at most 14,400).
|
||||
const WATERMARK_MAX_OFFSET: f64 = 20_000.0;
|
||||
/// `doc_optimize` with `target_bytes`: (max target ppi, max JPEG quality) per extra attempt, mildest first.
|
||||
/// The quality applies only to images the caller left on JPEG; zip and retain keep their encoding.
|
||||
const TARGET_LADDER: [(f64, u8); 5] = [(110.0, 45), (96.0, 35), (72.0, 28), (54.0, 20), (36.0, 12)];
|
||||
|
||||
/// Bookmarks in one page of the bookmark list, by default and at most.
|
||||
@@ -1143,10 +1144,10 @@ impl Automation {
|
||||
img.downsample = true;
|
||||
img.target_ppi = img.target_ppi.min(ppi);
|
||||
img.above_ppi = img.above_ppi.min(ppi * 1.5).max(img.target_ppi);
|
||||
img.compression = match img.compression {
|
||||
Compression::Jpeg(q) => Compression::Jpeg(q.min(quality)),
|
||||
_ => Compression::Jpeg(quality),
|
||||
};
|
||||
// Only a caller who chose JPEG gets a lower quality; zip and retain stay as asked.
|
||||
if let Compression::Jpeg(q) = img.compression {
|
||||
img.compression = Compression::Jpeg(q.min(quality));
|
||||
}
|
||||
}
|
||||
let (b2, r2) = self.session.optimized_bytes(id, &s, &discard).map_err(failed)?;
|
||||
attempts += 1;
|
||||
@@ -1160,6 +1161,8 @@ impl Automation {
|
||||
let mut extra = json!({});
|
||||
if let Some(t) = target {
|
||||
let (ppi, quality) = step_used.checked_sub(1).and_then(|k| TARGET_LADDER.get(k)).copied().unzip();
|
||||
let lossy = matches!(settings.color.compression, Compression::Jpeg(_)) || matches!(settings.gray.compression, Compression::Jpeg(_));
|
||||
let quality = quality.filter(|_| lossy);
|
||||
extra = json!({
|
||||
"target_bytes": t,
|
||||
"target_reached": bytes.len() <= t,
|
||||
|
||||
@@ -1303,7 +1303,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
.with(schema(json!({ "doc": doc() }), &["doc"])),
|
||||
t("doc_open_revision", "Open a revision", "Open saved revision `revision` (1 = the oldest) of a document as a new, unsaved document, to see the file as it was then.")
|
||||
.with(schema(json!({ "doc": doc(), "revision": { "type": "integer", "minimum": 1 } }), &["doc", "revision"])),
|
||||
t("doc_optimize", "Optimize PDF", "Write an optimized copy to `path` (Acrobat's PDF Optimizer). preset: standard (default; Reduce File Size's choices) or mobile (images to 96 ppi above 144, JPEG high quality); the options below change it. merge_fragments (default true) first puts a page picture drawn as many stacked image strips (as some PDF printers write it) back together into one image, so it can be resampled (merged_images, fragments_merged). color / gray: { downsample, ppi, above_ppi, compression: jpeg|zip|retain, quality 1–100 } (defaults: downsample to 150 ppi above 225, JPEG 60). Images are measured where pages draw them and replaced only when smaller. discard_*: thumbnails (default true), alternate_images (true), tags, print_settings; flate_unencoded (true); remove_invalid_links and remove_unreferenced_dests (true); images and forms the pages list but never draw are always dropped (unused_xobjects). discard: Remove Hidden Information categories (metadata, attachments, comments, form-fields, hidden-text, hidden-layers, bookmarks, links-actions-scripts, private-data). Signed documents are refused. target_bytes (optional): if the copy is larger, retry with up to 5 progressively stronger image settings (downsample to at most 110, 96, 72, 54, 36 ppi with JPEG quality at most 45, 35, 28, 20, 12; applied to colour and grey images) and stop at the first copy that fits; reports target_reached, attempts and target_settings, and writes the smallest copy with target_reached false when none fits. Without it nothing changes. The open document is unchanged.")
|
||||
t("doc_optimize", "Optimize PDF", "Write an optimized copy to `path` (Acrobat's PDF Optimizer). preset: standard (default; Reduce File Size's choices) or mobile (images to 96 ppi above 144, JPEG high quality); the options below change it. merge_fragments (default true) first puts a page picture drawn as many stacked image strips (as some PDF printers write it) back together into one image, so it can be resampled (merged_images, fragments_merged). color / gray: { downsample, ppi, above_ppi, compression: jpeg|zip|retain, quality 1–100 } (defaults: downsample to 150 ppi above 225, JPEG 60). Images are measured where pages draw them and replaced only when smaller. discard_*: thumbnails (default true), alternate_images (true), tags, print_settings; flate_unencoded (true); remove_invalid_links and remove_unreferenced_dests (true); images and forms the pages list but never draw are always dropped (unused_xobjects). discard: Remove Hidden Information categories (metadata, attachments, comments, form-fields, hidden-text, hidden-layers, bookmarks, links-actions-scripts, private-data). Signed documents are refused. target_bytes (optional): if the copy is larger, retry with up to 5 progressively stronger image settings (downsample to at most 110, 96, 72, 54, 36 ppi with applied to colour and grey images; images set to JPEG also get JPEG quality at most 45, 35, 28, 20, 12, while zip and retain images keep their encoding and only get the lower ppi) and stop at the first copy that fits; reports target_reached, attempts and target_settings, and writes the smallest copy with target_reached false when none fits. Without it nothing changes. The open document is unchanged.")
|
||||
.cmd("optimize.advanced")
|
||||
.with(schema(
|
||||
json!({
|
||||
|
||||
@@ -4791,6 +4791,14 @@ fn optimizing_to_a_target_size() {
|
||||
let again = ok(&mut a, "doc_optimize", json!({ "doc": doc, "path": "again.pdf" }));
|
||||
assert_eq!(again["bytes_after"].as_u64().unwrap(), base_len);
|
||||
assert!(matches!(a.call("doc_optimize", &json!({ "doc": doc, "path": "x.pdf", "target_bytes": 0 })), Err(ToolError::InvalidArgs(_))));
|
||||
// A caller who asked for lossless images keeps them: retries only tighten the ppi, no JPEG quality.
|
||||
let lossless = json!({ "compression": "zip" });
|
||||
let r = ok(&mut a, "doc_optimize", json!({ "doc": doc, "path": "zip.pdf", "color": lossless, "gray": lossless, "target_bytes": 1 }));
|
||||
assert_eq!(r["attempts"].as_u64(), Some(6), "{r}");
|
||||
assert!(r["target_settings"]["max_ppi"].is_number() && r["target_settings"]["max_jpeg_quality"].is_null(), "{r}");
|
||||
// A JPEG caller gets the quality step.
|
||||
let r = ok(&mut a, "doc_optimize", json!({ "doc": doc, "path": "jpg.pdf", "target_bytes": 1 }));
|
||||
assert!(r["target_settings"]["max_jpeg_quality"].is_number(), "{r}");
|
||||
// Confinement still applies.
|
||||
assert!(a.call("doc_optimize", &json!({ "doc": doc, "path": "../out.pdf", "target_bytes": 1000 })).is_err());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user