M4.44: PDF presets: built-ins in code, saved presets, Edit > PDF Presets

Built-in presets are generated in code (VectorCraft Default, High
Quality Print, Press Quality, Smallest File Size and PDF/X-1a, X-3,
X-4, the latter listed as not supported yet) and read-only. Saved
presets live with the preferences (Prefs.pdf_presets) and work as
`preset` wherever PDF options are taken: document.exportPdf,
pdfSettings, export/serialize as PDF and .ai saves.

pdf.preset.list / save / delete / export / import manage them
(.vcpdfpresets files; opening one imports it). The Save PDF dialog's
Save Preset… saves its settings under a name; Edit > PDF Presets…
(ui.pdfPresetsDialog) lists the presets with each one's description and
differences from the default, with New…, Edit… (the Save PDF dialog as
a preset editor, ui.pdfPresetDialog), Delete, Import… and Export….
document.pdfSettings now lists what differs from the chosen preset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-05 10:40:22 +07:00
co-authored by Claude Opus 5.5
parent 0ffaa427d1
commit 9aa29aa0fd
20 changed files with 1028 additions and 42 deletions
+2
View File
@@ -166,6 +166,8 @@ mod tests_import_color;
#[cfg(test)]
mod tests_import_options;
#[cfg(test)]
mod tests_presets;
#[cfg(test)]
mod tests_settings;
#[cfg(test)]
mod tests_stroke;
+74 -8
View File
@@ -1,9 +1,13 @@
//! PDF presets: named [`PdfSettings`]. The built-in ones are generated here (the app default
//! keeps the document editable).
//! PDF presets: named [`PdfSettings`]. The built-in ones are generated here (the app default,
//! which keeps the document editable, plus presets for print, press, small files and the PDF/X
//! standards); the user's own live in the app's preferences.
use serde::{Deserialize, Serialize};
use crate::PdfSettings;
use crate::{
BleedSettings, Compatibility, CompressionSettings, Downsample, ImageCodec, ImageCompression, JpegQuality, MonoCodec, MonoCompression,
PdfSettings, Standard,
};
/// The built-in preset every PDF export starts from.
pub const DEFAULT_PRESET: &str = "VectorCraft Default";
@@ -20,13 +24,75 @@ pub struct PdfPreset {
pub settings: PdfSettings,
}
/// Colour and greyscale images resampled (bicubic) to `ppi` above `above` ppi, compressed with
/// `codec`.
fn images(ppi: f64, above: f64, codec: ImageCodec, quality: JpegQuality) -> ImageCompression {
ImageCompression { downsample: Downsample::Bicubic, ppi, above_ppi: above, compression: codec, quality }
}
/// Compression with the same settings for colour and greyscale images, and CCITT Group 4
/// monochrome images resampled to `mono_ppi` above 1.5 × that.
fn compression(img: ImageCompression, mono_ppi: f64) -> CompressionSettings {
let mono = MonoCompression { downsample: Downsample::Bicubic, ppi: mono_ppi, above_ppi: mono_ppi * 1.5, compression: MonoCodec::CcittG4 };
CompressionSettings { color: img.clone(), gray: img, mono, compress_text: true }
}
/// Settings for print: PDF 1.6, images kept at 300 ppi, the document's bleed when `bleed`.
fn print(codec: ImageCodec, bleed: bool) -> PdfSettings {
PdfSettings {
compatibility: Compatibility::Pdf16,
compression: compression(images(300.0, 450.0, codec, JpegQuality::Maximum), 1200.0),
bleed: BleedSettings { use_document: bleed, ..Default::default() },
..Default::default()
}
}
/// A PDF/X preset: print settings for `standard` at PDF version `compatibility`.
fn pdf_x(standard: Standard, compatibility: Compatibility) -> PdfSettings {
PdfSettings { standard, compatibility, ..print(ImageCodec::Auto, true) }
}
/// Every built-in preset, the app default first.
pub fn builtin_presets() -> Vec<PdfPreset> {
vec![PdfPreset {
name: DEFAULT_PRESET.into(),
description: "Keeps the document editable: VectorCraft reopens the PDF with nothing lost. For files you'll edit again.".into(),
settings: PdfSettings { preserve_editing: true, ..Default::default() },
}]
let preset = |name: &str, description: &str, settings| PdfPreset { name: name.into(), description: description.into(), settings };
vec![
preset(
DEFAULT_PRESET,
"Keeps the document editable: VectorCraft reopens the PDF with nothing lost. For files you'll edit again.",
PdfSettings { preserve_editing: true, ..Default::default() },
),
preset("High Quality Print", "For desktop printers and proofs: images stay at print resolution.", print(ImageCodec::Auto, false)),
preset(
"Press Quality",
"For commercial printing: high-resolution images compressed losslessly, the document's bleed.",
print(ImageCodec::Zip, true),
),
preset(
"Smallest File Size",
"For screens, email and the web: images resampled to 100 ppi and compressed.",
PdfSettings {
compatibility: Compatibility::Pdf16,
fast_web_view: true,
compression: compression(images(100.0, 150.0, ImageCodec::Jpeg, JpegQuality::Medium), 300.0),
..Default::default()
},
),
preset(
"PDF/X-1a:2001",
"Print exchange with CMYK and spot colours only (PDF/X output is not supported yet).",
pdf_x(Standard::PdfX1a, Compatibility::Pdf14),
),
preset(
"PDF/X-3:2002",
"Print exchange allowing colour-managed colours (PDF/X output is not supported yet).",
pdf_x(Standard::PdfX3, Compatibility::Pdf14),
),
preset(
"PDF/X-4:2010",
"Print exchange keeping live transparency and layers (PDF/X output is not supported yet).",
pdf_x(Standard::PdfX4, Compatibility::Pdf16),
),
]
}
/// The built-in preset `name` names (any case; `default` is the app default).
+6
View File
@@ -424,6 +424,12 @@ impl PdfSettings {
within("advanced.fontSubsetPercent", self.advanced.font_subset_percent, 0.0, 100.0, "%")
}
/// Forget the passwords (presets never store them).
pub fn clear_passwords(&mut self) {
self.security.open_password.clear();
self.security.permissions_password.clear();
}
/// Options that are accepted but not applied by the writer yet, one warning each.
pub fn warnings(&self) -> Vec<String> {
let d = Self::default();
+22
View File
@@ -0,0 +1,22 @@
//! The built-in PDF presets: generated in code, valid, described, and round-tripping as JSON.
use crate::*;
#[test]
fn built_in_presets() {
let all = builtin_presets();
assert_eq!(all[0].name, DEFAULT_PRESET);
assert!(all[0].settings.preserve_editing, "the app default keeps the document editable");
assert_eq!(all.len(), 7);
for p in &all {
assert_eq!(p.settings.check_values(), Ok(()), "{}", p.name);
assert!(!p.description.is_empty());
let back: PdfPreset = serde_json::from_value(serde_json::to_value(p).unwrap()).unwrap();
assert_eq!(&back, p, "round-trips");
}
// PDF/X presets name standards the writer doesn't produce yet.
let x4 = builtin_preset("pdf/x-4:2010").unwrap();
assert!(matches!(x4.settings.check(), Err(PdfError::Unsupported(_))));
assert_eq!(builtin_preset("default").unwrap().name, DEFAULT_PRESET);
assert!(builtin_preset("Nope").is_none());
}