1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use super::video::AnimationEffect;
5
6#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
7#[serde(rename_all = "snake_case")]
8#[derive(Default)]
9pub enum CardDirection {
10 #[default]
11 Column,
12 Row,
13 ColumnReverse,
14 RowReverse,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
18#[serde(rename_all = "snake_case")]
19#[derive(Default)]
20pub enum CardAlign {
21 #[serde(alias = "flex-start", alias = "flex_start")]
22 #[default]
23 Start,
24 Center,
25 #[serde(alias = "flex-end", alias = "flex_end")]
26 End,
27 Stretch,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
31#[serde(rename_all = "snake_case")]
32#[derive(Default)]
33pub enum CardJustify {
34 #[serde(alias = "flex-start", alias = "flex_start")]
35 #[default]
36 Start,
37 Center,
38 #[serde(alias = "flex-end", alias = "flex_end")]
39 End,
40 #[serde(alias = "space-between")]
41 SpaceBetween,
42 #[serde(alias = "space-around")]
43 SpaceAround,
44 #[serde(alias = "space-evenly")]
45 SpaceEvenly,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
51pub struct TimelineStep {
52 pub at: f64,
54 #[serde(default, deserialize_with = "deserialize_animation_effects")]
56 pub animation: Vec<AnimationEffect>,
57 #[serde(default)]
61 pub style: Option<Box<crate::css::CssStyle>>,
62}
63
64#[derive(Debug, Clone, Default)]
65pub enum FontWeight {
66 #[default]
67 Normal,
68 Bold,
69 Weight(u16),
70}
71
72#[allow(dead_code)]
73impl FontWeight {
74 pub fn to_skia_weight(&self) -> i32 {
75 match self {
76 FontWeight::Normal => 400,
77 FontWeight::Bold => 700,
78 FontWeight::Weight(w) => *w as i32,
79 }
80 }
81}
82
83impl Serialize for FontWeight {
84 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
85 match self {
86 FontWeight::Normal => serializer.serialize_str("normal"),
87 FontWeight::Bold => serializer.serialize_str("bold"),
88 FontWeight::Weight(w) => serializer.serialize_u16(*w),
89 }
90 }
91}
92
93impl schemars::JsonSchema for FontWeight {
94 fn schema_name() -> String {
95 "FontWeight".to_string()
96 }
97
98 fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
99 serde_json::from_value(serde_json::json!({
100 "description": "Font weight — \"normal\", \"bold\", or a number.",
101 "anyOf": [
102 { "type": "string", "enum": ["normal", "bold"] },
103 { "type": "number" }
104 ]
105 }))
106 .expect("FontWeight's schema is a literal written here")
107 }
108}
109
110impl<'de> Deserialize<'de> for FontWeight {
111 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
112 struct FontWeightVisitor;
113 impl<'de> serde::de::Visitor<'de> for FontWeightVisitor {
114 type Value = FontWeight;
115 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
116 write!(f, "\"normal\", \"bold\", or a number 100-900")
117 }
118 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<FontWeight, E> {
119 match v {
120 "normal" => Ok(FontWeight::Normal),
121 "bold" => Ok(FontWeight::Bold),
122 _ => Err(E::custom(format!("unknown font weight: {}", v))),
123 }
124 }
125 fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<FontWeight, E> {
126 Ok(FontWeight::Weight(v as u16))
127 }
128 fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<FontWeight, E> {
129 Ok(FontWeight::Weight(v as u16))
130 }
131 fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<FontWeight, E> {
132 Ok(FontWeight::Weight(v as u16))
133 }
134 }
135 deserializer.deserialize_any(FontWeightVisitor)
136 }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
140#[serde(rename_all = "snake_case")]
141#[derive(Default)]
142pub enum FontStyleType {
143 #[default]
144 Normal,
145 Italic,
146 Oblique,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
150#[serde(rename_all = "snake_case")]
151#[derive(Default)]
152pub enum TextAlign {
153 #[default]
154 Left,
155 Center,
156 Right,
157}
158
159#[derive(Debug, Serialize, Deserialize, JsonSchema)]
160#[serde(rename_all = "snake_case")]
161#[derive(Default)]
162pub enum VerticalAlign {
163 Top,
164 #[default]
165 Middle,
166 Bottom,
167}
168
169#[derive(Debug, Clone, JsonSchema)]
171pub enum SizeDimension {
172 Fixed(f32),
173 Percent(f32),
174 Auto,
175}
176
177impl Serialize for SizeDimension {
178 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
179 match self {
180 SizeDimension::Fixed(v) => serializer.serialize_f32(*v),
181 SizeDimension::Percent(p) => serializer.serialize_str(&format!("{}%", p)),
182 SizeDimension::Auto => serializer.serialize_str("auto"),
183 }
184 }
185}
186
187impl<'de> Deserialize<'de> for SizeDimension {
188 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
189 struct SizeDimensionVisitor;
190 impl<'de> serde::de::Visitor<'de> for SizeDimensionVisitor {
191 type Value = SizeDimension;
192 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
193 write!(f, "a number, \"auto\", or \"50%\"")
194 }
195 fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<SizeDimension, E> {
196 Ok(SizeDimension::Fixed(v as f32))
197 }
198 fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<SizeDimension, E> {
199 Ok(SizeDimension::Fixed(v as f32))
200 }
201 fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<SizeDimension, E> {
202 Ok(SizeDimension::Fixed(v as f32))
203 }
204 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<SizeDimension, E> {
205 if v == "auto" {
206 Ok(SizeDimension::Auto)
207 } else if let Some(pct) = v.strip_suffix('%') {
208 pct.trim()
209 .parse::<f32>()
210 .map(SizeDimension::Percent)
211 .map_err(|_| E::custom(format!("invalid percentage: {}", v)))
212 } else {
213 Err(E::custom(format!(
214 "expected number, \"auto\", or \"N%\", got: {}",
215 v
216 )))
217 }
218 }
219 }
220 deserializer.deserialize_any(SizeDimensionVisitor)
221 }
222}
223
224pub fn deserialize_animation_effects<'de, D>(
225 deserializer: D,
226) -> Result<Vec<AnimationEffect>, D::Error>
227where
228 D: serde::Deserializer<'de>,
229{
230 use serde::de;
231
232 struct OneOrMany;
233
234 impl<'de> de::Visitor<'de> for OneOrMany {
235 type Value = Vec<AnimationEffect>;
236
237 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
238 f.write_str("a single animation effect or an array of animation effects")
239 }
240
241 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
242 where
243 A: de::SeqAccess<'de>,
244 {
245 Vec::deserialize(de::value::SeqAccessDeserializer::new(seq))
246 }
247
248 fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
249 where
250 M: de::MapAccess<'de>,
251 {
252 let effect = AnimationEffect::deserialize(de::value::MapAccessDeserializer::new(map))?;
253 Ok(vec![effect])
254 }
255 }
256
257 deserializer.deserialize_any(OneOrMany)
258}