qpdf/
writer.rs

1use std::{ffi::CString, path::Path, slice};
2
3use crate::{ObjectStreamMode, QPdf, Result, StreamDataMode, StreamDecodeLevel};
4
5/// Print permissions
6#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, PartialOrd)]
7pub enum PrintPermission {
8    #[default]
9    Full,
10    Low,
11    None,
12}
13
14impl From<PrintPermission> for qpdf_sys::qpdf_r3_print_e {
15    fn from(value: PrintPermission) -> Self {
16        match value {
17            PrintPermission::Full => qpdf_sys::qpdf_r3_print_e_qpdf_r3p_full,
18            PrintPermission::Low => qpdf_sys::qpdf_r3_print_e_qpdf_r3p_low,
19            PrintPermission::None => qpdf_sys::qpdf_r3_print_e_qpdf_r3p_none,
20        }
21    }
22}
23
24/// Encryption using RC4 with key length of 40 bits
25#[derive(Debug, Default, Clone, Eq, PartialEq)]
26pub struct EncryptionParamsR2 {
27    pub user_password: String,
28    pub owner_password: String,
29    pub allow_print: bool,
30    pub allow_modify: bool,
31    pub allow_extract: bool,
32    pub allow_annotate: bool,
33}
34
35/// Encryption using RC4 with key length of 128 bits.
36/// Minimal PDF version: 1.4.
37#[derive(Debug, Default, Clone, Eq, PartialEq)]
38pub struct EncryptionParamsR3 {
39    pub user_password: String,
40    pub owner_password: String,
41    pub allow_accessibility: bool,
42    pub allow_extract: bool,
43    pub allow_assemble: bool,
44    pub allow_annotate_and_form: bool,
45    pub allow_form_filling: bool,
46    pub allow_modify_other: bool,
47    pub allow_print: PrintPermission,
48}
49
50/// Encryption using RC4-128 or AES-256 algorithm and additional flag to encrypt metadata.
51/// Minimal PDF version: 1.5.
52#[derive(Debug, Default, Clone, Eq, PartialEq)]
53pub struct EncryptionParamsR4 {
54    pub user_password: String,
55    pub owner_password: String,
56    pub allow_accessibility: bool,
57    pub allow_extract: bool,
58    pub allow_assemble: bool,
59    pub allow_annotate_and_form: bool,
60    pub allow_form_filling: bool,
61    pub allow_modify_other: bool,
62    pub allow_print: PrintPermission,
63    pub encrypt_metadata: bool,
64    pub use_aes: bool,
65}
66
67/// Encryption using AES-256 algorithm and additional flag to encrypt metadata
68/// Minimal PDF version: 1.7. Is required for PDF 2.0.
69#[derive(Debug, Default, Clone, Eq, PartialEq)]
70pub struct EncryptionParamsR6 {
71    pub user_password: String,
72    pub owner_password: String,
73    pub allow_accessibility: bool,
74    pub allow_extract: bool,
75    pub allow_assemble: bool,
76    pub allow_annotate_and_form: bool,
77    pub allow_form_filling: bool,
78    pub allow_modify_other: bool,
79    pub allow_print: PrintPermission,
80    pub encrypt_metadata: bool,
81}
82
83/// Encryption parameters selector
84#[derive(Debug, Clone, Eq, PartialEq)]
85pub enum EncryptionParams {
86    /// R2 level, any PDF version
87    #[cfg(feature = "legacy")]
88    R2(EncryptionParamsR2),
89    /// R3 level, PDF version >= 1.4
90    #[cfg(feature = "legacy")]
91    R3(EncryptionParamsR3),
92    /// R4 level, PDF version >= 1.5
93    #[cfg(feature = "legacy")]
94    R4(EncryptionParamsR4),
95    /// R6 level, PDF version >= 1.7
96    R6(EncryptionParamsR6),
97}
98
99/// PDF writer with several customizable parameters
100pub struct QPdfWriter {
101    owner: QPdf,
102    compress_streams: Option<bool>,
103    preserve_unreferenced_objects: Option<bool>,
104    normalize_content: Option<bool>,
105    preserve_encryption: Option<bool>,
106    linearize: Option<bool>,
107    static_id: Option<bool>,
108    deterministic_id: Option<bool>,
109    min_pdf_version: Option<String>,
110    force_pdf_version: Option<String>,
111    stream_decode_level: Option<StreamDecodeLevel>,
112    object_stream_mode: Option<ObjectStreamMode>,
113    stream_data_mode: Option<StreamDataMode>,
114    encryption_params: Option<EncryptionParams>,
115}
116
117impl QPdfWriter {
118    pub(crate) fn new(owner: QPdf) -> Self {
119        QPdfWriter {
120            owner,
121            compress_streams: None,
122            preserve_unreferenced_objects: None,
123            normalize_content: None,
124            preserve_encryption: None,
125            linearize: None,
126            static_id: None,
127            deterministic_id: None,
128            min_pdf_version: None,
129            force_pdf_version: None,
130            stream_decode_level: None,
131            object_stream_mode: None,
132            stream_data_mode: None,
133            encryption_params: None,
134        }
135    }
136
137    fn process_params(&self) -> Result<()> {
138        unsafe {
139            if let Some(compress_streams) = self.compress_streams {
140                qpdf_sys::qpdf_set_compress_streams(self.owner.inner(), compress_streams.into());
141            }
142
143            if let Some(preserve_unreferenced_objects) = self.preserve_unreferenced_objects {
144                qpdf_sys::qpdf_set_preserve_unreferenced_objects(
145                    self.owner.inner(),
146                    preserve_unreferenced_objects.into(),
147                );
148            }
149
150            if let Some(normalize_content) = self.normalize_content {
151                qpdf_sys::qpdf_set_content_normalization(self.owner.inner(), normalize_content.into());
152            }
153
154            if let Some(preserve_encryption) = self.preserve_encryption {
155                qpdf_sys::qpdf_set_preserve_encryption(self.owner.inner(), preserve_encryption.into());
156            }
157
158            if let Some(linearize) = self.linearize {
159                qpdf_sys::qpdf_set_linearization(self.owner.inner(), linearize.into());
160            }
161
162            if let Some(static_id) = self.static_id {
163                qpdf_sys::qpdf_set_static_ID(self.owner.inner(), static_id.into());
164            }
165
166            if let Some(deterministic_id) = self.deterministic_id {
167                qpdf_sys::qpdf_set_deterministic_ID(self.owner.inner(), deterministic_id.into());
168            }
169
170            if let Some(stream_decode_level) = self.stream_decode_level {
171                qpdf_sys::qpdf_set_decode_level(self.owner.inner(), stream_decode_level.as_qpdf_enum());
172            }
173
174            if let Some(object_stream_mode) = self.object_stream_mode {
175                qpdf_sys::qpdf_set_object_stream_mode(self.owner.inner(), object_stream_mode.as_qpdf_enum());
176            }
177
178            if let Some(stream_data_mode) = self.stream_data_mode {
179                qpdf_sys::qpdf_set_stream_data_mode(self.owner.inner(), stream_data_mode.as_qpdf_enum());
180            }
181
182            if let Some(ref version) = self.min_pdf_version {
183                let version = CString::new(version.as_str())?;
184                self.owner
185                    .wrap_ffi_call(|| qpdf_sys::qpdf_set_minimum_pdf_version(self.owner.inner(), version.as_ptr()))?;
186            }
187            if let Some(ref version) = self.force_pdf_version {
188                let version = CString::new(version.as_str())?;
189                self.owner
190                    .wrap_ffi_call(|| qpdf_sys::qpdf_force_pdf_version(self.owner.inner(), version.as_ptr()))?;
191            }
192            if let Some(ref params) = self.encryption_params {
193                self.set_encryption_params(params)?;
194            }
195        }
196        Ok(())
197    }
198
199    fn set_encryption_params(&self, params: &EncryptionParams) -> Result<()> {
200        match params {
201            #[cfg(feature = "legacy")]
202            EncryptionParams::R2(r2) => {
203                let user_password = CString::new(r2.user_password.as_str())?;
204                let owner_password = CString::new(r2.owner_password.as_str())?;
205                unsafe {
206                    self.owner.wrap_ffi_call(|| {
207                        qpdf_sys::qpdf_set_r2_encryption_parameters_insecure(
208                            self.owner.inner(),
209                            user_password.as_ptr(),
210                            owner_password.as_ptr(),
211                            r2.allow_print.into(),
212                            r2.allow_modify.into(),
213                            r2.allow_extract.into(),
214                            r2.allow_annotate.into(),
215                        )
216                    })?;
217                }
218            }
219            #[cfg(feature = "legacy")]
220            EncryptionParams::R3(r3) => {
221                let user_password = CString::new(r3.user_password.as_str())?;
222                let owner_password = CString::new(r3.owner_password.as_str())?;
223                unsafe {
224                    self.owner.wrap_ffi_call(|| {
225                        qpdf_sys::qpdf_set_r3_encryption_parameters_insecure(
226                            self.owner.inner(),
227                            user_password.as_ptr(),
228                            owner_password.as_ptr(),
229                            r3.allow_accessibility.into(),
230                            r3.allow_extract.into(),
231                            r3.allow_assemble.into(),
232                            r3.allow_annotate_and_form.into(),
233                            r3.allow_form_filling.into(),
234                            r3.allow_modify_other.into(),
235                            r3.allow_print.into(),
236                        )
237                    })?;
238                }
239            }
240            #[cfg(feature = "legacy")]
241            EncryptionParams::R4(r4) => {
242                let user_password = CString::new(r4.user_password.as_str())?;
243                let owner_password = CString::new(r4.owner_password.as_str())?;
244                unsafe {
245                    self.owner.wrap_ffi_call(|| {
246                        qpdf_sys::qpdf_set_r4_encryption_parameters_insecure(
247                            self.owner.inner(),
248                            user_password.as_ptr(),
249                            owner_password.as_ptr(),
250                            r4.allow_accessibility.into(),
251                            r4.allow_extract.into(),
252                            r4.allow_assemble.into(),
253                            r4.allow_annotate_and_form.into(),
254                            r4.allow_form_filling.into(),
255                            r4.allow_modify_other.into(),
256                            r4.allow_print.into(),
257                            r4.encrypt_metadata.into(),
258                            r4.use_aes.into(),
259                        )
260                    })?;
261                }
262            }
263            EncryptionParams::R6(r6) => {
264                let user_password = CString::new(r6.user_password.as_str())?;
265                let owner_password = CString::new(r6.owner_password.as_str())?;
266                unsafe {
267                    self.owner.wrap_ffi_call(|| {
268                        qpdf_sys::qpdf_set_r6_encryption_parameters2(
269                            self.owner.inner(),
270                            user_password.as_ptr(),
271                            owner_password.as_ptr(),
272                            r6.allow_accessibility.into(),
273                            r6.allow_extract.into(),
274                            r6.allow_assemble.into(),
275                            r6.allow_annotate_and_form.into(),
276                            r6.allow_form_filling.into(),
277                            r6.allow_modify_other.into(),
278                            r6.allow_print.into(),
279                            r6.encrypt_metadata.into(),
280                        )
281                    })?;
282                }
283            }
284        }
285        Ok(())
286    }
287
288    /// Write PDF to a file
289    pub fn write<P>(&self, path: P) -> Result<()>
290    where
291        P: AsRef<Path>,
292    {
293        let filename = CString::new(path.as_ref().to_string_lossy().as_ref())?;
294
295        let inner = self.owner.inner();
296
297        self.owner
298            .wrap_ffi_call(|| unsafe { qpdf_sys::qpdf_init_write(inner, filename.as_ptr()) })?;
299
300        self.process_params()?;
301
302        self.owner.wrap_ffi_call(|| unsafe { qpdf_sys::qpdf_write(inner) })
303    }
304
305    /// Write PDF to a memory and return it in a Vec
306    pub fn write_to_memory(&self) -> Result<Vec<u8>> {
307        let inner = self.owner.inner();
308        self.owner
309            .wrap_ffi_call(|| unsafe { qpdf_sys::qpdf_init_write_memory(inner) })?;
310
311        self.process_params()?;
312
313        self.owner.wrap_ffi_call(|| unsafe { qpdf_sys::qpdf_write(inner) })?;
314
315        let buffer = unsafe { qpdf_sys::qpdf_get_buffer(inner) };
316        let buffer_len = unsafe { qpdf_sys::qpdf_get_buffer_length(inner) };
317
318        unsafe { Ok(slice::from_raw_parts(buffer, buffer_len as _).to_vec()) }
319    }
320
321    /// Enable or disable stream compression
322    pub fn compress_streams(&mut self, flag: bool) -> &mut Self {
323        self.compress_streams = Some(flag);
324        self
325    }
326
327    /// Set minimum PDF version
328    pub fn minimum_pdf_version(&mut self, version: &str) -> &mut Self {
329        self.min_pdf_version = Some(version.to_owned());
330        self
331    }
332
333    /// Force a specific PDF version
334    pub fn force_pdf_version(&mut self, version: &str) -> &mut Self {
335        self.force_pdf_version = Some(version.to_owned());
336        self
337    }
338
339    /// Set stream decode level
340    pub fn stream_decode_level(&mut self, level: StreamDecodeLevel) -> &mut Self {
341        self.stream_decode_level = Some(level);
342        self
343    }
344
345    /// Set object stream mode
346    pub fn object_stream_mode(&mut self, mode: ObjectStreamMode) -> &mut Self {
347        self.object_stream_mode = Some(mode);
348        self
349    }
350
351    /// Set stream data mode
352    pub fn stream_data_mode(&mut self, mode: StreamDataMode) -> &mut Self {
353        self.stream_data_mode = Some(mode);
354        self
355    }
356
357    /// Set a flag indicating whether to preserve the unreferenced objects
358    pub fn preserve_unreferenced_objects(&mut self, flag: bool) -> &mut Self {
359        self.preserve_unreferenced_objects = Some(flag);
360        self
361    }
362
363    /// Set a flag indicating whether to normalized contents
364    pub fn normalize_content(&mut self, flag: bool) -> &mut Self {
365        self.normalize_content = Some(flag);
366        self
367    }
368
369    /// Preserve or remove encryption
370    pub fn preserve_encryption(&mut self, flag: bool) -> &mut Self {
371        self.preserve_encryption = Some(flag);
372        self
373    }
374
375    /// Enable or disable linearization
376    pub fn linearize(&mut self, flag: bool) -> &mut Self {
377        self.linearize = Some(flag);
378        self
379    }
380
381    // Enable or disable static ID
382    pub fn static_id(&mut self, flag: bool) -> &mut Self {
383        self.static_id = Some(flag);
384        self
385    }
386
387    // Enable or disable deterministic ID
388    pub fn deterministic_id(&mut self, flag: bool) -> &mut Self {
389        self.deterministic_id = Some(flag);
390        self
391    }
392
393    // Set encryption parameters
394    pub fn encryption_params(&mut self, params: EncryptionParams) -> &mut Self {
395        self.encryption_params = Some(params);
396        self
397    }
398}