1use super::{ColorSpace, MAX_PATTERN_COMPONENTS, Rgb};
12use pdfrum_object::Name;
13use smallvec::SmallVec;
14use std::sync::Arc;
15
16#[derive(Debug, Clone, PartialEq)]
20pub struct ColorValue {
21 pub space: Option<Arc<ColorSpace>>,
24 pub components: SmallVec<[f32; 4]>,
26 pub pattern: Option<Box<PatternValue>>,
28}
29
30impl Default for ColorValue {
31 fn default() -> Self {
34 Self {
35 space: None,
36 components: SmallVec::from_slice(&[0.0]),
37 pattern: None,
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq)]
45pub struct PatternValue {
46 pub name: Name,
48 pub components: SmallVec<[f32; 4]>,
51 pub loaded: Option<Arc<crate::pattern::Pattern>>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
63pub enum SetComponentsError {
64 #[error("need {needed} colour components, got {got}")]
66 TooFew {
67 needed: usize,
69 got: usize,
71 },
72 #[error("a pattern colour is not set by components")]
74 PatternSpace,
75}
76
77impl ColorValue {
78 pub fn set_space(&mut self, space: Arc<ColorSpace>) {
84 self.components = space.default_color().into();
85 self.pattern = None;
86 self.space = Some(space);
87 }
88
89 pub fn set_components(&mut self, values: &[f32]) -> Result<(), SetComponentsError> {
101 let space = self
102 .space
103 .get_or_insert_with(|| Arc::new(ColorSpace::DeviceGray));
104 if space.n_components() > values.len() {
105 return Err(SetComponentsError::TooFew {
106 needed: space.n_components(),
107 got: values.len(),
108 });
109 }
110 if matches!(**space, ColorSpace::Pattern(_)) {
111 return Err(SetComponentsError::PatternSpace);
114 }
115 self.components = SmallVec::from_slice(values);
116 Ok(())
117 }
118
119 pub fn set_stock(&mut self, space: ColorSpace, values: &[f32]) {
122 let space = Arc::new(space);
123 self.set_space(Arc::clone(&space));
124 if space.n_components() <= values.len() {
125 self.components = SmallVec::from_slice(values);
126 }
127 }
128
129 pub fn set_pattern(
140 &mut self,
141 name: Name,
142 values: &[f32],
143 loaded: Option<Arc<crate::pattern::Pattern>>,
144 ) {
145 if values.len() > MAX_PATTERN_COMPONENTS {
146 return;
147 }
148 if !self.is_pattern() {
149 self.space = Some(Arc::new(ColorSpace::Pattern(Box::default())));
150 }
151 self.pattern = Some(Box::new(PatternValue {
152 name,
153 components: SmallVec::from_slice(values),
154 loaded,
155 }));
156 }
157
158 #[must_use]
161 pub fn to_rgb(&self) -> Option<Rgb> {
162 let space = self.space.as_ref()?;
163 if let ColorSpace::Pattern(pattern_space) = &**space {
164 let value = self.pattern.as_ref()?;
165 return pattern_space.to_rgb(&value.components);
166 }
167 space.try_to_rgb(&self.components)
168 }
169
170 #[must_use]
172 pub fn is_pattern(&self) -> bool {
173 self.space
174 .as_ref()
175 .is_some_and(|s| matches!(**s, ColorSpace::Pattern(_)))
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 #![allow(
185 clippy::unreadable_literal,
186 clippy::float_cmp,
187 clippy::indexing_slicing,
188 clippy::cast_precision_loss,
189 clippy::cast_possible_truncation,
190 reason = "test fixtures quote oracle vectors verbatim and compare exactly"
191 )]
192
193 use super::{ColorSpace, ColorValue, SetComponentsError};
194 use pdfrum_object::Name;
195 use smallvec::SmallVec;
196 use std::sync::Arc;
197
198 #[test]
199 fn the_initial_colour_is_black_in_device_gray() {
200 let c = ColorValue::default();
201 assert_eq!(&c.components[..], &[0.0]);
202 assert!(c.space.is_none());
203 }
204
205 #[test]
206 fn installing_a_space_resets_the_components() {
207 let mut c = ColorValue::default();
208 c.set_stock(ColorSpace::DeviceRgb, &[1.0, 0.5, 0.25]);
209 assert_eq!(&c.components[..], &[1.0, 0.5, 0.25]);
210 c.set_space(Arc::new(ColorSpace::DeviceGray));
212 assert_eq!(&c.components[..], &[0.0]);
213 }
214
215 #[test]
216 fn a_pattern_space_refuses_component_operands() {
217 let mut c = ColorValue::default();
218 c.set_space(Arc::new(ColorSpace::Pattern(Box::default())));
219 let before = c.components.clone();
220 assert_eq!(
221 c.set_components(&[0.5]),
222 Err(SetComponentsError::PatternSpace)
223 );
224 assert_eq!(c.components, before);
225 }
226
227 #[test]
228 fn too_few_components_change_nothing_at_all() {
229 let mut c = ColorValue::default();
230 c.set_stock(ColorSpace::DeviceCmyk, &[0.1, 0.2, 0.3, 0.4]);
231 assert_eq!(
232 c.set_components(&[0.9, 0.9]),
233 Err(SetComponentsError::TooFew { needed: 4, got: 2 })
234 );
235 assert_eq!(&c.components[..], &[0.1, 0.2, 0.3, 0.4]);
236 assert!(c.set_components(&[0.5, 0.5, 0.5, 0.5]).is_ok());
238 assert_eq!(&c.components[..], &[0.5, 0.5, 0.5, 0.5]);
239 }
240
241 #[test]
242 fn components_with_no_space_install_device_gray() {
243 let mut c = ColorValue {
244 space: None,
245 components: SmallVec::new(),
246 pattern: None,
247 };
248 assert!(c.set_components(&[0.75]).is_ok());
249 assert_eq!(
250 c.space.as_deref(),
251 Some(&ColorSpace::DeviceGray),
252 "an unset space becomes DeviceGray"
253 );
254 }
255
256 #[test]
257 fn a_pattern_operand_vector_over_sixteen_is_refused_outright() {
258 let mut c = ColorValue::default();
261 c.set_space(Arc::new(ColorSpace::Pattern(Box::default())));
262 c.set_pattern(Name::from("P0"), &[0.5, 0.25], None);
263 c.set_pattern(Name::from("P1"), &[1.0; 17], None);
264 let p = c.pattern.as_ref().expect("pattern");
265 assert_eq!(p.name.as_bytes(), b"P0", "the whole call is refused");
266 assert_eq!(&p.components[..], &[0.5, 0.25]);
267 }
268
269 #[test]
270 fn installing_a_pattern_installs_the_pattern_space() {
271 let mut c = ColorValue::default();
278 assert!(!c.is_pattern());
279 c.set_pattern(Name::from("P1"), &[], None);
280 assert!(c.is_pattern(), "the stock /Pattern space is installed");
281 let mut with_base = ColorValue::default();
284 with_base.set_space(Arc::new(ColorSpace::Pattern(Box::new(
285 super::super::PatternSpace {
286 base: Some(Box::new(ColorSpace::DeviceRgb)),
287 },
288 ))));
289 with_base.set_pattern(Name::from("P1"), &[1.0, 0.0, 0.0], None);
290 assert_eq!(
291 with_base.to_rgb().map(super::Rgb::to_bytes),
292 Some([255, 0, 0])
293 );
294 }
295
296 #[test]
297 fn a_pattern_colour_resolves_through_its_base_space() {
298 let mut c = ColorValue::default();
299 c.set_space(Arc::new(ColorSpace::Pattern(Box::new(
300 super::super::PatternSpace {
301 base: Some(Box::new(ColorSpace::DeviceGray)),
302 },
303 ))));
304 assert!(c.is_pattern());
305 assert!(c.to_rgb().is_none());
307 c.set_pattern(Name::from("P0"), &[0.5], None);
308 let rgb = c.to_rgb().expect("colour");
309 assert!((rgb.r - 0.5).abs() < 1e-6);
310 }
311}