1use crate::error::Result;
7use crate::objects::{Dictionary, Object};
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum SoftMaskType {
13 Alpha,
15 Luminosity,
17}
18
19impl SoftMaskType {
20 pub fn pdf_name(&self) -> &'static str {
22 match self {
23 SoftMaskType::Alpha => "Alpha",
24 SoftMaskType::Luminosity => "Luminosity",
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
31pub enum TransferFunction {
32 Identity,
34 Custom(String),
36 FunctionArray(Vec<f64>),
38}
39
40impl TransferFunction {
41 pub fn to_pdf_object(&self) -> Object {
43 match self {
44 TransferFunction::Identity => Object::Name("Identity".to_string()),
45 TransferFunction::Custom(name) => Object::Name(name.clone()),
46 TransferFunction::FunctionArray(values) => {
47 Object::Array(values.iter().map(|&v| Object::Real(v)).collect())
48 }
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct SoftMask {
56 pub mask_type: SoftMaskType,
58
59 pub group_ref: Option<String>,
61
62 pub background_color: Option<Vec<f64>>,
64
65 pub transfer_function: Option<TransferFunction>,
67
68 pub bbox: Option<[f64; 4]>,
70}
71
72impl Default for SoftMask {
73 fn default() -> Self {
74 Self::none()
75 }
76}
77
78impl SoftMask {
79 pub fn none() -> Self {
81 Self {
82 mask_type: SoftMaskType::Alpha,
83 group_ref: None,
84 background_color: None,
85 transfer_function: None,
86 bbox: None,
87 }
88 }
89
90 pub fn alpha(group_ref: String) -> Self {
92 Self {
93 mask_type: SoftMaskType::Alpha,
94 group_ref: Some(group_ref),
95 background_color: None,
96 transfer_function: None,
97 bbox: None,
98 }
99 }
100
101 pub fn luminosity(group_ref: String) -> Self {
103 Self {
104 mask_type: SoftMaskType::Luminosity,
105 group_ref: Some(group_ref),
106 background_color: None,
107 transfer_function: None,
108 bbox: None,
109 }
110 }
111
112 pub fn with_background_color(mut self, color: Vec<f64>) -> Self {
114 self.background_color = Some(color);
115 self
116 }
117
118 pub fn with_transfer_function(mut self, func: TransferFunction) -> Self {
120 self.transfer_function = Some(func);
121 self
122 }
123
124 pub fn with_bbox(mut self, bbox: [f64; 4]) -> Self {
126 self.bbox = Some(bbox);
127 self
128 }
129
130 pub fn is_none(&self) -> bool {
132 self.group_ref.is_none()
133 }
134
135 pub fn to_pdf_dictionary(&self) -> Result<Dictionary> {
137 if self.group_ref.is_none() {
139 let mut dict = Dictionary::new();
140 dict.set("Type", Object::Name("Mask".to_string()));
141 dict.set("S", Object::Name("None".to_string()));
142 return Ok(dict);
143 }
144
145 let mut dict = Dictionary::new();
146 dict.set("Type", Object::Name("Mask".to_string()));
147 dict.set("S", Object::Name(self.mask_type.pdf_name().to_string()));
148
149 if let Some(ref group_ref) = self.group_ref {
151 dict.set("G", Object::Name(group_ref.clone()));
152 }
153
154 if let Some(ref bc) = self.background_color {
156 let color_array: Vec<Object> = bc.iter().map(|&c| Object::Real(c)).collect();
157 dict.set("BC", Object::Array(color_array));
158 }
159
160 if let Some(ref tr) = self.transfer_function {
162 dict.set("TR", tr.to_pdf_object());
163 }
164
165 if let Some(bbox) = self.bbox {
167 let bbox_array = vec![
168 Object::Real(bbox[0]),
169 Object::Real(bbox[1]),
170 Object::Real(bbox[2]),
171 Object::Real(bbox[3]),
172 ];
173 dict.set("BBox", Object::Array(bbox_array));
174 }
175
176 Ok(dict)
177 }
178
179 pub fn to_pdf_string(&self) -> String {
181 if self.is_none() {
182 "/None".to_string()
183 } else {
184 format!("/SM{}", self.group_ref.as_ref().unwrap_or(&"1".to_string()))
186 }
187 }
188}
189
190impl fmt::Display for SoftMask {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 if self.is_none() {
193 write!(f, "SoftMask::None")
194 } else {
195 write!(f, "SoftMask::{:?}", self.mask_type)
196 }
197 }
198}
199
200#[derive(Debug, Clone)]
202pub struct SoftMaskState {
203 pub mask: SoftMask,
205
206 pub saved_masks: Vec<SoftMask>,
208}
209
210impl Default for SoftMaskState {
211 fn default() -> Self {
212 Self::new()
213 }
214}
215
216impl SoftMaskState {
217 pub fn new() -> Self {
219 Self {
220 mask: SoftMask::none(),
221 saved_masks: Vec::new(),
222 }
223 }
224
225 pub fn set_mask(&mut self, mask: SoftMask) {
227 self.mask = mask;
228 }
229
230 pub fn push_mask(&mut self, mask: SoftMask) {
232 self.saved_masks.push(self.mask.clone());
233 self.mask = mask;
234 }
235
236 pub fn pop_mask(&mut self) -> Option<SoftMask> {
238 if let Some(mask) = self.saved_masks.pop() {
239 let current = self.mask.clone();
240 self.mask = mask;
241 Some(current)
242 } else {
243 None
244 }
245 }
246
247 pub fn clear(&mut self) {
249 self.mask = SoftMask::none();
250 self.saved_masks.clear();
251 }
252
253 pub fn is_active(&self) -> bool {
255 !self.mask.is_none()
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_soft_mask_none() {
265 let mask = SoftMask::none();
266 assert!(mask.is_none());
267 assert_eq!(mask.to_pdf_string(), "/None");
268 }
269
270 #[test]
271 fn test_soft_mask_alpha() {
272 let mask = SoftMask::alpha("Group1".to_string());
273 assert!(!mask.is_none());
274 assert_eq!(mask.mask_type, SoftMaskType::Alpha);
275 assert_eq!(mask.group_ref, Some("Group1".to_string()));
276 }
277
278 #[test]
279 fn test_soft_mask_luminosity() {
280 let mask = SoftMask::luminosity("Group2".to_string());
281 assert!(!mask.is_none());
282 assert_eq!(mask.mask_type, SoftMaskType::Luminosity);
283 assert_eq!(mask.group_ref, Some("Group2".to_string()));
284 }
285
286 #[test]
287 fn test_soft_mask_with_background() {
288 let mask = SoftMask::alpha("Group1".to_string()).with_background_color(vec![1.0, 1.0, 1.0]);
289
290 assert_eq!(mask.background_color, Some(vec![1.0, 1.0, 1.0]));
291 }
292
293 #[test]
294 fn test_soft_mask_with_transfer_function() {
295 let mask = SoftMask::alpha("Group1".to_string())
296 .with_transfer_function(TransferFunction::Identity);
297
298 assert!(mask.transfer_function.is_some());
299 }
300
301 #[test]
302 fn test_soft_mask_with_bbox() {
303 let mask = SoftMask::alpha("Group1".to_string()).with_bbox([0.0, 0.0, 100.0, 100.0]);
304
305 assert_eq!(mask.bbox, Some([0.0, 0.0, 100.0, 100.0]));
306 }
307
308 #[test]
309 fn test_soft_mask_to_dictionary() {
310 let mask = SoftMask::luminosity("Group1".to_string())
311 .with_background_color(vec![0.5, 0.5, 0.5])
312 .with_transfer_function(TransferFunction::Identity)
313 .with_bbox([0.0, 0.0, 200.0, 200.0]);
314
315 let dict = mask.to_pdf_dictionary().unwrap();
316
317 assert_eq!(dict.get("Type"), Some(&Object::Name("Mask".to_string())));
318 assert_eq!(dict.get("S"), Some(&Object::Name("Luminosity".to_string())));
319 assert!(dict.contains_key("G"));
320 assert!(dict.contains_key("BC"));
321 assert!(dict.contains_key("TR"));
322 assert!(dict.contains_key("BBox"));
323 }
324
325 #[test]
326 fn test_soft_mask_none_dictionary() {
327 let mask = SoftMask::none();
328 let dict = mask.to_pdf_dictionary().unwrap();
329
330 assert_eq!(dict.get("Type"), Some(&Object::Name("Mask".to_string())));
331 assert_eq!(dict.get("S"), Some(&Object::Name("None".to_string())));
332 }
333
334 #[test]
335 fn test_soft_mask_state() {
336 let mut state = SoftMaskState::new();
337 assert!(state.mask.is_none());
338
339 let mask1 = SoftMask::alpha("Group1".to_string());
340 state.set_mask(mask1);
341 assert!(!state.mask.is_none());
342
343 let mask2 = SoftMask::luminosity("Group2".to_string());
344 state.push_mask(mask2);
345 assert_eq!(state.saved_masks.len(), 1);
346
347 let popped = state.pop_mask();
348 assert!(popped.is_some());
349 assert_eq!(state.mask.group_ref, Some("Group1".to_string()));
350 }
351
352 #[test]
353 fn test_transfer_function_to_pdf() {
354 let identity = TransferFunction::Identity;
355 assert_eq!(
356 identity.to_pdf_object(),
357 Object::Name("Identity".to_string())
358 );
359
360 let custom = TransferFunction::Custom("Custom1".to_string());
361 assert_eq!(custom.to_pdf_object(), Object::Name("Custom1".to_string()));
362
363 let array = TransferFunction::FunctionArray(vec![0.0, 0.5, 1.0]);
364 if let Object::Array(arr) = array.to_pdf_object() {
365 assert_eq!(arr.len(), 3);
366 } else {
367 panic!("Expected array");
368 }
369 }
370}