1use palette::{
3 encoding::{Linear, Srgb},
4 rgb::Rgb,
5 Alpha,
6};
7use serde::{
8 de::{self, SeqAccess, Unexpected, Visitor},
9 Deserialize, Deserializer, Serialize,
10};
11use std::{convert::TryFrom, fmt, num::ParseIntError};
12
13pub(crate) type RgbaComponents = (f64, f64, f64, f64);
14type HexString = String;
15
16#[derive(Debug, Clone, PartialEq, Serialize)]
38#[serde(untagged)]
39pub enum Colour {
40 Seq(RgbaComponents),
41 Hex(HexString),
43}
44
45impl<'de> Deserialize<'de> for Colour {
46 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
47 where
48 D: Deserializer<'de>,
49 {
50 struct ColourVisitor;
51 impl<'de> Visitor<'de> for ColourVisitor {
52 type Value = Colour;
53
54 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
55 write!(
56 formatter,
57 "4 values (r, g, b, a) in the range 0.0-1.0 or 0-255, or a hex colour"
58 )
59 }
60
61 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
62 where
63 E: de::Error,
64 {
65 match decode_hex(s) {
66 Ok(c) => Ok(Colour::Seq(c)),
67 Err(_) => Err(de::Error::invalid_value(Unexpected::Str(s), &self)),
68 }
69 }
70
71 fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
72 where
73 V: SeqAccess<'de>,
74 {
75 let r: f64 = seq
76 .next_element()?
77 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
78 let g: f64 = seq
79 .next_element()?
80 .ok_or_else(|| de::Error::invalid_length(1, &self))?;
81 let b: f64 = seq
82 .next_element()?
83 .ok_or_else(|| de::Error::invalid_length(2, &self))?;
84 let a: f64 = seq
85 .next_element()?
86 .ok_or_else(|| de::Error::invalid_length(3, &self))?;
87
88 if (0f64..=1.0).contains(&r)
89 && (0f64..=1.0).contains(&g)
90 && (0f64..=1.0).contains(&b)
91 && (0f64..=1.0).contains(&a)
92 {
93 Ok(Colour::Seq((r, g, b, a)))
94 } else if (0f64..=255.0).contains(&r)
95 && (0f64..=255.0).contains(&g)
96 && (0f64..=255.0).contains(&b)
97 && (0f64..=255.0).contains(&a)
98 {
99 Ok(Colour::Seq((
100 (r / 255.0),
101 (g / 255.0),
102 (b / 255.0),
103 (a / 255.0),
104 )))
105 } else {
106 Err(de::Error::invalid_value(Unexpected::Seq, &self))
107 }
108 }
109 }
110 deserializer.deserialize_any(ColourVisitor)
111 }
112}
113
114fn decode_hex(s: &str) -> Result<RgbaComponents, ParseIntError> {
115 let s = s.trim_start_matches('#');
116 let mut v: Vec<u8> = (0..s.len())
117 .step_by(2)
118 .map(|i| u8::from_str_radix(&s[i..i + 2], 16))
119 .collect::<Result<_, _>>()?;
120 if v.len() == 3 {
121 v.extend_from_slice(&[255])
122 }
123 Ok((
124 v[0] as f64 / 255.0,
125 v[1] as f64 / 255.0,
126 v[2] as f64 / 255.0,
127 v[3] as f64 / 255.0,
128 ))
129}
130
131impl From<RgbaComponents> for Colour {
132 fn from(comp: RgbaComponents) -> Self {
133 Self::Seq(comp)
134 }
135}
136
137impl From<(u8, u8, u8, u8)> for Colour {
138 fn from(vals: (u8, u8, u8, u8)) -> Self {
139 Self::Seq((
140 vals.0 as f64 / 255.0,
141 vals.1 as f64 / 255.0,
142 vals.2 as f64 / 255.0,
143 vals.3 as f64 / 255.0,
144 ))
145 }
146}
147
148impl TryFrom<&str> for Colour {
149 type Error = ParseIntError;
150 fn try_from(s: &str) -> Result<Self, Self::Error> {
151 Ok(Self::Seq(decode_hex(s)?))
152 }
153}
154
155impl From<Colour> for RgbaComponents {
156 fn from(col: Colour) -> Self {
157 match col {
158 Colour::Seq(comp) => comp,
159 Colour::Hex(str) => panic!("Prefer Colour::try_from({:?})", str),
162 }
163 }
164}
165
166impl From<Alpha<Rgb<Linear<Srgb>, f64>, f64>> for Colour {
167 fn from(comp: Alpha<Rgb<Linear<Srgb>, f64>, f64>) -> Self {
168 Self::Seq(comp.into_components())
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use std::convert::TryInto;
176
177 #[test]
178 fn test_decode_hex() {
179 let expected_comp: RgbaComponents = (1., 0., 0., 1.);
180 assert_eq!(decode_hex("ff0000ff").unwrap(), expected_comp);
181 assert_eq!(decode_hex("FF0000FF").unwrap(), expected_comp);
182 assert_eq!(decode_hex("#ff0000ff").unwrap(), expected_comp);
183 assert_eq!(decode_hex("#ff0000").unwrap(), expected_comp);
185 }
186
187 #[test]
188 fn test_colour_from() {
189 assert_eq!(
190 Colour::try_from("ff0000ff").unwrap(),
191 "ff0000ff".try_into().unwrap()
192 );
193 assert_eq!(
194 Colour::try_from("ff0000ff").unwrap(),
195 (255, 0, 0, 255).into()
196 );
197 assert!(Colour::try_from("ff0000gg").is_err());
198 }
199
200 #[test]
201 fn test_colour_is_deserialized() {
202 let expected_col = Colour::Seq((1., 0., 0., 1.));
203 let expected_comp: RgbaComponents = (1., 0., 0., 1.);
204
205 let s = "\"ff0000ff\"";
206 let col: Colour = serde_json::from_str(s).unwrap();
207 assert_eq!(col, expected_col);
208 let comp: RgbaComponents = col.into();
209 assert_eq!(comp, expected_comp);
210
211 let s = r#"
212 [255, 0, 0, 255]
213 "#;
214 let col: Colour = serde_json::from_str(s).unwrap();
215 assert_eq!(col, expected_col);
216 let comp: RgbaComponents = col.into();
217 assert_eq!(comp, expected_comp);
218
219 let s = r#"
220 [1.0, 0.0, 0.0, 1.0]
221 "#;
222 let col: Colour = serde_json::from_str(s).unwrap();
223 assert_eq!(col, expected_col);
224 let comp: RgbaComponents = col.into();
225 assert_eq!(comp, expected_comp);
226 }
227
228 #[test]
229 #[should_panic]
230 fn test_hex_colour_panics() {
231 let _: RgbaComponents = Colour::Hex("ff0000ff".to_string()).into();
232 }
233
234 #[test]
235 fn test_colour_deserialized_fails() {
236 let s = "\"ff0000gg\"";
237 let col: Result<Colour, _> = serde_json::from_str(s);
238 let expected_msg =
239 "invalid value: string \"ff0000gg\", expected 4 values (r, g, b, a) in the range 0.0-1.0 or 0-255, or a hex colour at line 1 column 10";
240 if let Err(err) = col {
241 assert_eq!(format!("{}", err), expected_msg.to_string())
242 };
243 }
244}