1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/// Options for rendering PDFs
#[derive(derive_builder::Builder)]
pub struct RenderOptions {
    #[builder(default = "DPI::Uniform(150)")]
    /// Resolution in dots per inch
    pub resolution: DPI,
    #[builder(setter(into, strip_option), default)]
    /// Scale pages to a certain number of pixels
    pub scale: Option<Scale>,
    #[builder(default)]
    /// Render pages in grayscale
    pub greyscale: bool,
    #[builder(setter(into, strip_option), default)]
    /// Crop a specific section of the page
    pub crop: Option<Crop>,
    #[builder(setter(into, strip_option), default)]
    /// Password to unlock encrypted PDFs
    pub password: Option<Password>,
    /// Use pdftocairo instead of pdftoppm
    #[builder(default)]
    pub pdftocairo: bool,
}

impl Default for RenderOptions {
    fn default() -> Self {
        Self {
            resolution: DPI::Uniform(150),
            scale: None,
            greyscale: false,
            crop: None,
            password: None,
            pdftocairo: false,
        }
    }
}

impl RenderOptions {
    pub fn to_cli_args(&self) -> Vec<String> {
        let mut args = vec![];

        match self.resolution {
            DPI::Uniform(dpi) => {
                args.push("-r".to_string());
                args.push(dpi.to_string());
            }
            DPI::XY(dpi_x, dpi_y) => {
                args.push("-rx".to_string());
                args.push(dpi_x.to_string());
                args.push("-ry".to_string());
                args.push(dpi_y.to_string());
            }
        }

        if let Some(scale) = &self.scale {
            match scale {
                Scale::Uniform(scale) => {
                    args.push("-scale-to".to_string());
                    args.push(scale.to_string());
                }
                Scale::X(scale_x) => {
                    args.push("-scale-to-x".to_string());
                    args.push(scale_x.to_string());
                }
                Scale::Y(scale_y) => {
                    args.push("-scale-to-y".to_string());
                    args.push(scale_y.to_string());
                }
                Scale::XY(scale_x, scale_y) => {
                    args.push("-scale-to-x".to_string());
                    args.push(scale_x.to_string());
                    args.push("-scale-to-y".to_string());
                    args.push(scale_y.to_string());
                }
            }
        }

        if self.greyscale {
            args.push("-gray".to_string());
        }

        if let Some(crop) = &self.crop {
            args.push("-cropbox".to_string());
            let (x, y) = (crop.inner.x, crop.inner.y);
            let (width, height) = (crop.inner.width, crop.inner.height);
            args.push("-x".to_string());
            args.push(x.to_string());
            args.push("-y".to_string());
            args.push(y.to_string());
            args.push("-W".to_string());
            args.push(width.to_string());
            args.push("-H".to_string());
            args.push(height.to_string());
        }

        if let Some(password) = &self.password {
            match password {
                Password::User(password) => {
                    args.push("-upw".to_string());
                    args.push(password.clone());
                }
                Password::Owner(password) => {
                    args.push("-opw".to_string());
                    args.push(password.clone());
                }
            }
        }

        args
    }
}

/// Password to unlock encrypted PDFs
#[derive(Debug, Clone)]
pub enum Password {
    User(String),
    Owner(String),
}

/// Specifies resolution in terms of dots per inch
#[derive(Debug, Clone)]
pub enum DPI {
    /// DPI for both axes
    Uniform(u32),
    /// DPI for x and y axis
    XY(u32, u32),
}

/// Scales pages to a certain number of pixels
#[derive(Debug, Clone)]
pub enum Scale {
    /// scales each page to fit within scale-to*scale-to pixel box
    Uniform(u32),
    /// scales each page horizontally to fit in scale-to-x pixels
    X(u32),
    /// scales each page vertically to fit in scale-to-y pixels
    Y(u32),
    ///  scales each page to fit within scale-to-x*scale-to-y pixel box
    XY(u32, u32),
}

/// Crop a specific section of the page
#[derive(Debug, Clone)]
pub struct Crop {
    inner: image::math::Rect,
}

impl Crop {
    pub fn new(x1: u32, y1: u32, x2: u32, y2: u32) -> Self {
        let (min_x, max_x) = match x1 < x2 {
            true => (x1, x2),
            false => (x2, x1),
        };

        let (min_y, max_y) = match y1 < y2 {
            true => (y1, y2),
            false => (y2, y1),
        };

        Self {
            inner: image::math::Rect {
                x: min_x,
                y: min_y,
                width: max_x - min_x,
                height: max_y - min_y,
            },
        }
    }

    pub fn from_top_left(width: u32, height: u32, top_left: (u32, u32)) -> Self {
        Self {
            inner: image::math::Rect {
                x: top_left.0,
                y: top_left.1,
                width,
                height,
            },
        }
    }

    pub fn square(size: u32, top_left: (u32, u32)) -> Self {
        Self {
            inner: image::math::Rect {
                x: top_left.0,
                y: top_left.1,
                width: size,
                height: size,
            },
        }
    }
}