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)]
129pub(crate) struct TransparencyGroupState {
130 pub group: TransparencyGroup,
132}
133
134impl TransparencyGroupState {
135 pub fn new(group: TransparencyGroup) -> Self {
137 Self { group }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_transparency_group_creation() {
147 let group = TransparencyGroup::new();
148 assert!(!group.isolated);
149 assert!(!group.knockout);
150 assert_eq!(group.opacity, 1.0);
151 assert!(matches!(group.blend_mode, BlendMode::Normal));
152 }
153
154 #[test]
155 fn test_isolated_group() {
156 let group = TransparencyGroup::isolated();
157 assert!(group.isolated);
158 assert!(!group.knockout);
159 }
160
161 #[test]
162 fn test_knockout_group() {
163 let group = TransparencyGroup::knockout();
164 assert!(!group.isolated);
165 assert!(group.knockout);
166 }
167
168 #[test]
169 fn test_group_builder() {
170 let group = TransparencyGroup::new()
171 .with_isolated(true)
172 .with_knockout(true)
173 .with_blend_mode(BlendMode::Multiply)
174 .with_opacity(0.5)
175 .with_color_space("DeviceRGB");
176
177 assert!(group.isolated);
178 assert!(group.knockout);
179 assert_eq!(group.opacity, 0.5);
180 assert!(matches!(group.blend_mode, BlendMode::Multiply));
181 assert_eq!(group.color_space, Some("DeviceRGB".to_string()));
182 }
183
184 #[test]
185 fn test_opacity_clamping() {
186 let group1 = TransparencyGroup::new().with_opacity(1.5);
187 assert_eq!(group1.opacity, 1.0);
188
189 let group2 = TransparencyGroup::new().with_opacity(-0.5);
190 assert_eq!(group2.opacity, 0.0);
191 }
192
193 #[test]
194 fn test_to_dict() {
195 let group = TransparencyGroup::new()
196 .with_isolated(true)
197 .with_knockout(true)
198 .with_color_space("DeviceCMYK");
199
200 let dict = group.to_dict();
201
202 assert_eq!(dict.get("Type"), Some(&Object::Name("Group".into())));
204 assert_eq!(dict.get("S"), Some(&Object::Name("Transparency".into())));
205
206 assert_eq!(dict.get("I"), Some(&Object::Boolean(true)));
208 assert_eq!(dict.get("K"), Some(&Object::Boolean(true)));
209 assert_eq!(dict.get("CS"), Some(&Object::Name("DeviceCMYK".into())));
210 }
211
212 #[test]
213 fn test_default_dict() {
214 let group = TransparencyGroup::new();
215 let dict = group.to_dict();
216
217 assert_eq!(dict.get("Type"), Some(&Object::Name("Group".into())));
219 assert_eq!(dict.get("S"), Some(&Object::Name("Transparency".into())));
220 assert!(dict.get("I").is_none());
221 assert!(dict.get("K").is_none());
222 assert!(dict.get("CS").is_none());
223 }
224}