Skip to main content

paper_age/
convenience.rs

1//! Convenience function for end-to-end PDF generation
2
3use std::fmt;
4use std::io::BufRead;
5
6use age::secrecy::SecretString;
7
8use crate::builder;
9use crate::encryption;
10use crate::page::PageSize;
11
12/// Errors that can occur during PDF generation
13#[derive(Debug)]
14pub enum PaperAgeError {
15    /// The plaintext data could not be encrypted
16    Encryption(String),
17    /// The PDF document could not be initialized
18    DocumentInit(String),
19    /// The PDF could not be created (e.g. QR code too large)
20    PdfCreation(String),
21}
22
23impl fmt::Display for PaperAgeError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            PaperAgeError::Encryption(msg) => write!(f, "Encryption failed: {msg}"),
27            PaperAgeError::DocumentInit(msg) => write!(f, "Document initialization failed: {msg}"),
28            PaperAgeError::PdfCreation(msg) => write!(f, "PDF creation failed: {msg}"),
29        }
30    }
31}
32
33impl std::error::Error for PaperAgeError {}
34
35/// Generate a PaperAge PDF from plaintext data and a passphrase.
36///
37/// This is a high-level convenience function that handles encryption and PDF
38/// generation in a single call.
39///
40/// # Arguments
41///
42/// * `title` - The document title (appears in the PDF and its metadata)
43/// * `data` - A buffered reader providing the plaintext data to encrypt
44/// * `passphrase` - The passphrase used to encrypt the data
45/// * `notes_label` - Label for the notes field (defaults to `"Passphrase:"`)
46/// * `skip_notes_line` - Whether to omit the notes placeholder line (defaults to `false`)
47/// * `page_size` - The page size to use (defaults to [`PageSize::A4`])
48/// * `grid` - Whether to draw a debug grid on the page (defaults to `false`)
49///
50/// # Returns
51///
52/// The PDF file contents as a `Vec<u8>`, or a [`PaperAgeError`] describing
53/// what went wrong.
54///
55/// # Example
56///
57/// ```no_run
58/// use paper_age::convenience::create_pdf;
59/// use paper_age::page::PageSize;
60///
61/// let pdf_bytes = create_pdf(
62///     "My Secret".to_string(),
63///     &mut &b"secret data to encrypt"[..],
64///     "hunter2",
65///     None,
66///     None,
67///     None,
68///     None,
69/// ).expect("PDF generation failed");
70/// ```
71pub fn create_pdf(
72    title: String,
73    data: &mut dyn BufRead,
74    passphrase: &str,
75    notes_label: Option<String>,
76    skip_notes_line: Option<bool>,
77    page_size: Option<PageSize>,
78    grid: Option<bool>,
79) -> Result<Vec<u8>, PaperAgeError> {
80    let notes_label = notes_label.unwrap_or_else(|| "Passphrase:".to_string());
81    let skip_notes_line = skip_notes_line.unwrap_or(false);
82    let page_size = page_size.unwrap_or(PageSize::A4);
83    let grid = grid.unwrap_or(false);
84
85    let passphrase_secret = SecretString::from(passphrase.to_owned());
86
87    let (_plaintext_len, encrypted) = encryption::encrypt_plaintext(data, passphrase_secret)
88        .map_err(|e| PaperAgeError::Encryption(e.to_string()))?;
89
90    let pdf = builder::Document::new(title, page_size)
91        .map_err(|e| PaperAgeError::DocumentInit(e.to_string()))?;
92
93    let bytes = pdf
94        .create_pdf(grid, notes_label, skip_notes_line, encrypted)
95        .map_err(|e| PaperAgeError::PdfCreation(e.to_string()))?;
96
97    Ok(bytes)
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_create_pdf_defaults() {
106        let result = create_pdf(
107            "Test Document".to_string(),
108            &mut &b"hello world"[..],
109            "passphrase",
110            None,
111            None,
112            None,
113            None,
114        );
115        assert!(result.is_ok());
116        let bytes = result.unwrap();
117        assert!(!bytes.is_empty());
118    }
119
120    #[test]
121    fn test_create_pdf_with_options() {
122        let result = create_pdf(
123            "Custom Document".to_string(),
124            &mut &b"secret data"[..],
125            "hunter2",
126            Some("Recovery key:".to_string()),
127            Some(true),
128            Some(PageSize::Letter),
129            Some(true),
130        );
131        assert!(result.is_ok());
132    }
133
134    #[test]
135    fn test_create_pdf_empty_data() {
136        let result = create_pdf(
137            "Empty".to_string(),
138            &mut &b""[..],
139            "passphrase",
140            None,
141            None,
142            None,
143            None,
144        );
145        assert!(result.is_ok());
146    }
147
148    #[test]
149    fn test_error_display() {
150        assert_eq!(
151            PaperAgeError::Encryption("bad key".to_string()).to_string(),
152            "Encryption failed: bad key"
153        );
154        assert_eq!(
155            PaperAgeError::DocumentInit("missing font".to_string()).to_string(),
156            "Document initialization failed: missing font"
157        );
158        assert_eq!(
159            PaperAgeError::PdfCreation("QR too large".to_string()).to_string(),
160            "PDF creation failed: QR too large"
161        );
162    }
163}