1use crate::transparency::SoftMask;
11use std::sync::Arc;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17pub enum BlendMode {
18 #[default]
20 Normal,
21 Compatible,
23 Multiply,
25 Screen,
27 Overlay,
29 Darken,
31 Lighten,
33 ColorDodge,
35 ColorBurn,
37 HardLight,
39 SoftLight,
41 Difference,
43 Exclusion,
45 Hue,
47 Saturation,
49 Color,
51 Luminosity,
53}
54
55impl BlendMode {
56 #[must_use]
58 pub fn from_name(name: &[u8]) -> Self {
59 match name {
60 b"Multiply" => Self::Multiply,
61 b"Screen" => Self::Screen,
62 b"Overlay" => Self::Overlay,
63 b"Darken" => Self::Darken,
64 b"Lighten" => Self::Lighten,
65 b"ColorDodge" => Self::ColorDodge,
66 b"ColorBurn" => Self::ColorBurn,
67 b"HardLight" => Self::HardLight,
68 b"SoftLight" => Self::SoftLight,
69 b"Difference" => Self::Difference,
70 b"Exclusion" => Self::Exclusion,
71 b"Hue" => Self::Hue,
72 b"Saturation" => Self::Saturation,
73 b"Color" => Self::Color,
74 b"Luminosity" => Self::Luminosity,
75 b"Compatible" => Self::Compatible,
76 _ => Self::Normal,
78 }
79 }
80
81 #[must_use]
86 pub fn needs_backdrop(self) -> bool {
87 !matches!(self, Self::Normal | Self::Compatible | Self::Multiply)
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
96pub enum RenderIntent {
97 #[default]
99 Unknown,
100 Absolute,
102 Saturation,
104 Perceptual,
106}
107
108impl RenderIntent {
109 #[must_use]
112 pub fn from_name(name: &[u8]) -> Self {
113 match name.get(..4) {
114 Some(b"Abso") => Self::Absolute,
115 Some(b"Satu") => Self::Saturation,
116 Some(b"Perc") => Self::Perceptual,
117 _ => Self::Unknown,
118 }
119 }
120}
121
122#[expect(
129 clippy::struct_excessive_bools,
130 reason = "each flag is an independent /ExtGState key, not a mode"
131)]
132#[derive(Debug, Clone, PartialEq)]
133pub struct GeneralState {
134 pub fill_alpha: f32,
136 pub stroke_alpha: f32,
138 pub blend: BlendMode,
140 pub soft_mask: Option<Arc<SoftMask>>,
142 pub transfer: Option<Arc<crate::transfer::TransferFunc>>,
145
146 pub render_intent: RenderIntent,
149 pub stroke_overprint: bool,
151 pub fill_overprint: bool,
153 pub overprint_mode: i64,
155 pub flatness: f32,
157 pub smoothness: f32,
159 pub stroke_adjust: bool,
161 pub alpha_is_shape: bool,
163 pub text_knockout: bool,
165}
166
167impl Default for GeneralState {
168 fn default() -> Self {
169 Self {
170 fill_alpha: 1.0,
171 stroke_alpha: 1.0,
172 blend: BlendMode::Normal,
173 soft_mask: None,
174 transfer: None,
175 render_intent: RenderIntent::Unknown,
176 stroke_overprint: false,
177 fill_overprint: false,
178 overprint_mode: 0,
179 flatness: 0.0,
180 smoothness: 0.0,
181 stroke_adjust: false,
182 alpha_is_shape: false,
183 text_knockout: false,
184 }
185 }
186}
187
188impl GeneralState {
189 pub fn enter_transparency_group(&mut self) {
195 self.blend = BlendMode::Normal;
196 self.stroke_alpha = 1.0;
197 self.fill_alpha = 1.0;
198 self.soft_mask = None;
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 #![allow(
208 clippy::unreadable_literal,
209 clippy::float_cmp,
210 clippy::indexing_slicing,
211 clippy::cast_precision_loss,
212 clippy::cast_possible_truncation,
213 reason = "test fixtures quote oracle vectors verbatim and compare exactly"
214 )]
215
216 use super::{BlendMode, GeneralState, RenderIntent};
217
218 #[test]
219 fn an_unknown_blend_name_is_normal() {
220 assert_eq!(BlendMode::from_name(b"Multiply"), BlendMode::Multiply);
221 assert_eq!(BlendMode::from_name(b"Luminosity"), BlendMode::Luminosity);
222 assert_eq!(BlendMode::from_name(b"Normal"), BlendMode::Normal);
223 assert_eq!(BlendMode::from_name(b"NotAMode"), BlendMode::Normal);
224 assert_eq!(BlendMode::from_name(b""), BlendMode::Normal);
225 assert_eq!(BlendMode::from_name(b"multiply"), BlendMode::Normal);
227 }
228
229 #[test]
230 fn backdrop_is_needed_past_multiply() {
231 assert!(!BlendMode::Normal.needs_backdrop());
232 assert!(!BlendMode::Compatible.needs_backdrop());
233 assert!(!BlendMode::Multiply.needs_backdrop());
234 assert!(BlendMode::Screen.needs_backdrop());
235 assert!(BlendMode::Luminosity.needs_backdrop());
236 }
237
238 #[test]
239 fn render_intents_match_on_four_bytes() {
240 assert_eq!(
241 RenderIntent::from_name(b"AbsoluteColorimetric"),
242 RenderIntent::Absolute
243 );
244 assert_eq!(RenderIntent::from_name(b"Abso"), RenderIntent::Absolute);
245 assert_eq!(
246 RenderIntent::from_name(b"Perceptual"),
247 RenderIntent::Perceptual
248 );
249 assert_eq!(RenderIntent::from_name(b"Rel"), RenderIntent::Unknown);
250 assert_eq!(RenderIntent::from_name(b""), RenderIntent::Unknown);
251 }
252
253 #[test]
254 fn entering_a_group_clears_exactly_four_parameters() {
255 let mut s = GeneralState {
256 fill_alpha: 0.5,
257 stroke_alpha: 0.25,
258 blend: BlendMode::Multiply,
259 overprint_mode: 7,
260 flatness: 3.0,
261 ..GeneralState::default()
262 };
263 s.enter_transparency_group();
264 assert!((s.fill_alpha - 1.0).abs() < 1e-6);
265 assert!((s.stroke_alpha - 1.0).abs() < 1e-6);
266 assert_eq!(s.blend, BlendMode::Normal);
267 assert!(s.soft_mask.is_none());
268 assert_eq!(s.overprint_mode, 7);
270 assert!((s.flatness - 3.0).abs() < 1e-6);
271 }
272
273 #[test]
274 fn the_defaults_are_fully_opaque_and_unblended() {
275 let s = GeneralState::default();
276 assert!((s.fill_alpha - 1.0).abs() < 1e-6);
277 assert!((s.stroke_alpha - 1.0).abs() < 1e-6);
278 assert_eq!(s.blend, BlendMode::Normal);
279 assert!(s.soft_mask.is_none());
280 assert!(s.transfer.is_none());
281 }
282}