oxidize_pdf/graphics/
transparency.rs1use super::state::BlendMode;
7use crate::objects::{Dictionary, Object};
8
9#[derive(Debug, Clone)]
11pub struct TransparencyGroup {
12 pub isolated: bool,
16
17 pub knockout: bool,
21
22 pub blend_mode: BlendMode,
24
25 pub opacity: f32,
27
28 pub color_space: Option<String>,
30}
31
32impl Default for TransparencyGroup {
33 fn default() -> Self {
34 Self {
35 isolated: false,
36 knockout: false,
37 blend_mode: BlendMode::Normal,
38 opacity: 1.0,
39 color_space: None,
40 }
41 }
42}
43
44impl TransparencyGroup {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn isolated() -> Self {
52 Self {
53 isolated: true,
54 ..Default::default()
55 }
56 }
57
58 pub fn knockout() -> Self {
60 Self {
61 knockout: true,
62 ..Default::default()
63 }
64 }
65
66 pub fn with_isolated(mut self, isolated: bool) -> Self {
68 self.isolated = isolated;
69 self
70 }
71
72 pub fn with_knockout(mut self, knockout: bool) -> Self {
74 self.knockout = knockout;
75 self
76 }
77
78 pub fn with_blend_mode(mut self, blend_mode: BlendMode) -> Self {
80 self.blend_mode = blend_mode;
81 self
82 }
83
84 pub fn with_opacity(mut self, opacity: f32) -> Self {
86 self.opacity = opacity.clamp(0.0, 1.0);
87 self
88 }
89
90 pub fn with_color_space(mut self, color_space: impl Into<String>) -> Self {
92 self.color_space = Some(color_space.into());
93 self
94 }
95
96 pub fn to_dict(&self) -> Dictionary {
98 let mut dict = Dictionary::new();
99
100 dict.set("Type", Object::Name("Group".into()));
102 dict.set("S", Object::Name("Transparency".into()));
103
104 if self.isolated {
106 dict.set("I", Object::Boolean(true));
107 }
108
109 if self.knockout {
110 dict.set("K", Object::Boolean(true));
111 }
112
113 if let Some(ref cs) = self.color_space {
114 dict.set("CS", Object::Name(cs.clone()));
115 }
116
117 dict
118 }
119}
120
121#[derive(Debug, Clone)]
123pub(crate) struct TransparencyGroupState {
124 pub group: TransparencyGroup,
126
127 pub saved_state: Vec<u8>,
129
130 #[allow(dead_code)]
132 pub content: Vec<u8>,
133}
134
135impl TransparencyGroupState {
136 pub fn new(group: TransparencyGroup) -> Self {
138 Self {
139 group,
140 saved_state: Vec::new(),
141 content: Vec::new(),
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_transparency_group_creation() {
152 let group = TransparencyGroup::new();
153 assert!(!group.isolated);
154 assert!(!group.knockout);
155 assert_eq!(group.opacity, 1.0);
156 assert!(matches!(group.blend_mode, BlendMode::Normal));
157 }
158
159 #[test]
160 fn test_isolated_group() {
161 let group = TransparencyGroup::isolated();
162 assert!(group.isolated);
163 assert!(!group.knockout);
164 }
165
166 #[test]
167 fn test_knockout_group() {
168 let group = TransparencyGroup::knockout();
169 assert!(!group.isolated);
170 assert!(group.knockout);
171 }
172
173 #[test]
174 fn test_group_builder() {
175 let group = TransparencyGroup::new()
176 .with_isolated(true)
177 .with_knockout(true)
178 .with_blend_mode(BlendMode::Multiply)
179 .with_opacity(0.5)
180 .with_color_space("DeviceRGB");
181
182 assert!(group.isolated);
183 assert!(group.knockout);
184 assert_eq!(group.opacity, 0.5);
185 assert!(matches!(group.blend_mode, BlendMode::Multiply));
186 assert_eq!(group.color_space, Some("DeviceRGB".to_string()));
187 }
188
189 #[test]
190 fn test_opacity_clamping() {
191 let group1 = TransparencyGroup::new().with_opacity(1.5);
192 assert_eq!(group1.opacity, 1.0);
193
194 let group2 = TransparencyGroup::new().with_opacity(-0.5);
195 assert_eq!(group2.opacity, 0.0);
196 }
197
198 #[test]
199 fn test_to_dict() {
200 let group = TransparencyGroup::new()
201 .with_isolated(true)
202 .with_knockout(true)
203 .with_color_space("DeviceCMYK");
204
205 let dict = group.to_dict();
206
207 assert_eq!(dict.get("Type"), Some(&Object::Name("Group".into())));
209 assert_eq!(dict.get("S"), Some(&Object::Name("Transparency".into())));
210
211 assert_eq!(dict.get("I"), Some(&Object::Boolean(true)));
213 assert_eq!(dict.get("K"), Some(&Object::Boolean(true)));
214 assert_eq!(dict.get("CS"), Some(&Object::Name("DeviceCMYK".into())));
215 }
216
217 #[test]
218 fn test_default_dict() {
219 let group = TransparencyGroup::new();
220 let dict = group.to_dict();
221
222 assert_eq!(dict.get("Type"), Some(&Object::Name("Group".into())));
224 assert_eq!(dict.get("S"), Some(&Object::Name("Transparency".into())));
225 assert!(dict.get("I").is_none());
226 assert!(dict.get("K").is_none());
227 assert!(dict.get("CS").is_none());
228 }
229}