avenger_vega/marks/
values.rs

1use crate::error::AvengerVegaError;
2use avenger::marks::value::{
3    ColorOrGradient, Gradient, GradientStop, LinearGradient, RadialGradient,
4};
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::borrow::Cow;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(untagged)]
10pub enum StrokeDashSpec {
11    String(String),
12    Array(Vec<f32>),
13}
14
15impl StrokeDashSpec {
16    pub fn to_array(&self) -> Result<Cow<Vec<f32>>, AvengerVegaError> {
17        match self {
18            StrokeDashSpec::Array(a) => Ok(Cow::Borrowed(a)),
19            StrokeDashSpec::String(s) => {
20                let clean_dash_str = s.replace(',', " ");
21                let mut dashes: Vec<f32> = Vec::new();
22                for s in clean_dash_str.split_whitespace() {
23                    let d = s
24                        .parse::<f32>()
25                        .map_err(|_| AvengerVegaError::InvalidDashString(s.to_string()))?
26                        .abs();
27                    dashes.push(d);
28                }
29                Ok(Cow::Owned(dashes))
30            }
31        }
32    }
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36#[serde(untagged)]
37pub enum CssColorOrGradient {
38    Color(String),
39    Gradient(CssGradient),
40}
41
42impl CssColorOrGradient {
43    pub fn to_color_or_grad(
44        &self,
45        opacity: f32,
46        gradients: &mut Vec<Gradient>,
47    ) -> Result<ColorOrGradient, AvengerVegaError> {
48        match self {
49            CssColorOrGradient::Color(c) => {
50                let c = csscolorparser::parse(c)?;
51                Ok(ColorOrGradient::Color([
52                    c.r as f32,
53                    c.g as f32,
54                    c.b as f32,
55                    c.a as f32 * opacity,
56                ]))
57            }
58            CssColorOrGradient::Gradient(grad) => {
59                // Build gradient
60                let grad = match grad.gradient {
61                    VegaGradientType::Linear => Gradient::LinearGradient(LinearGradient {
62                        x0: grad.x1.unwrap_or(0.0),
63                        y0: grad.y1.unwrap_or(0.0),
64                        x1: grad.x2.unwrap_or(1.0),
65                        y1: grad.y2.unwrap_or(0.0),
66                        stops: grad
67                            .stops
68                            .iter()
69                            .map(|s| s.to_grad_stop(opacity))
70                            .collect::<Result<Vec<_>, AvengerVegaError>>()?,
71                    }),
72                    VegaGradientType::Radial => Gradient::RadialGradient(RadialGradient {
73                        x0: grad.x1.unwrap_or(0.5),
74                        y0: grad.y1.unwrap_or(0.5),
75                        x1: grad.x2.unwrap_or(0.5),
76                        y1: grad.y2.unwrap_or(0.5),
77                        r0: grad.r1.unwrap_or(0.0),
78                        r1: grad.r2.unwrap_or(0.5),
79                        stops: grad
80                            .stops
81                            .iter()
82                            .map(|s| s.to_grad_stop(opacity))
83                            .collect::<Result<Vec<_>, AvengerVegaError>>()?,
84                    }),
85                };
86
87                // Check if we already have it
88                let pos = match gradients.iter().position(|g| g == &grad) {
89                    Some(pos) => pos,
90                    None => {
91                        let pos = gradients.len();
92                        gradients.push(grad);
93                        pos
94                    }
95                };
96                Ok(ColorOrGradient::GradientIndex(pos as u32))
97            }
98        }
99    }
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103pub struct CssGradient {
104    #[serde(default)]
105    gradient: VegaGradientType,
106    x1: Option<f32>,
107    y1: Option<f32>,
108    x2: Option<f32>,
109    y2: Option<f32>,
110    r1: Option<f32>,
111    r2: Option<f32>,
112    stops: Vec<CssGradientStop>,
113}
114
115#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
116#[serde(rename_all = "lowercase")]
117pub enum VegaGradientType {
118    #[default]
119    Linear,
120    Radial,
121}
122
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124pub struct CssGradientStop {
125    offset: f32,
126    color: String,
127}
128
129impl CssGradientStop {
130    pub fn to_grad_stop(&self, opacity: f32) -> Result<GradientStop, AvengerVegaError> {
131        let c = csscolorparser::parse(&self.color)?;
132        Ok(GradientStop {
133            offset: self.offset,
134            color: [c.r as f32, c.g as f32, c.b as f32, c.a as f32 * opacity],
135        })
136    }
137}
138
139/// Helper struct that will not drop null values on round trip (de)serialization
140#[derive(Debug, Clone, PartialEq, Eq, Default)]
141pub enum MissingNullOrValue<V> {
142    #[default]
143    Missing,
144    Null,
145    Value(V),
146}
147
148impl<V> MissingNullOrValue<V> {
149    pub fn is_missing(&self) -> bool {
150        matches!(self, MissingNullOrValue::Missing)
151    }
152
153    pub fn is_null(&self) -> bool {
154        matches!(self, MissingNullOrValue::Null)
155    }
156
157    pub fn as_option(&self) -> Option<&V> {
158        match self {
159            MissingNullOrValue::Missing | MissingNullOrValue::Null => None,
160            MissingNullOrValue::Value(v) => Some(v),
161        }
162    }
163}
164
165impl<V> From<Option<V>> for MissingNullOrValue<V> {
166    fn from(opt: Option<V>) -> MissingNullOrValue<V> {
167        match opt {
168            Some(v) => MissingNullOrValue::Value(v),
169            None => MissingNullOrValue::Null,
170        }
171    }
172}
173
174impl<'de, V: Deserialize<'de>> Deserialize<'de> for MissingNullOrValue<V> {
175    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
176    where
177        D: Deserializer<'de>,
178    {
179        Option::deserialize(deserializer).map(Into::into)
180    }
181}
182
183impl<V: Serialize> Serialize for MissingNullOrValue<V> {
184    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
185    where
186        S: Serializer,
187    {
188        match self {
189            MissingNullOrValue::Missing => None::<Option<String>>.serialize(serializer),
190            MissingNullOrValue::Null => serde_json::Value::Null.serialize(serializer),
191            MissingNullOrValue::Value(v) => v.serialize(serializer),
192        }
193    }
194}