1use std::fmt;
14
15use bevy::color::Srgba;
16use bevy::math::Vec2;
17use serde::Deserialize;
18use serde::de::{self, Deserializer, Visitor};
19
20use crate::canvas::parse_css_color;
21use crate::protocol::{animatable::Animatable, decode_warn};
22
23mod path;
24#[cfg(test)]
25mod tests;
26
27pub use path::{PathData, PathSeg};
28
29#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
49#[serde(rename_all = "camelCase", default)]
50pub struct ShapeAttrs {
51 pub x: Option<Animatable<f32>>,
53 pub y: Option<Animatable<f32>>,
54 pub width: Option<Animatable<f32>>,
55 pub height: Option<Animatable<f32>>,
56 pub cx: Option<Animatable<f32>>,
57 pub cy: Option<Animatable<f32>>,
58 pub r: Option<Animatable<f32>>,
59 pub rx: Option<Animatable<f32>>,
60 pub ry: Option<Animatable<f32>>,
61 pub x1: Option<Animatable<f32>>,
62 pub y1: Option<Animatable<f32>>,
63 pub x2: Option<Animatable<f32>>,
64 pub y2: Option<Animatable<f32>>,
65 #[serde(deserialize_with = "de_points")]
68 pub points: Option<Vec<Vec2>>,
69 #[serde(deserialize_with = "de_path")]
71 pub d: Option<PathData>,
72
73 #[serde(deserialize_with = "de_paint")]
77 pub fill: Option<ShapePaint>,
78 #[serde(deserialize_with = "de_paint")]
80 pub stroke: Option<ShapePaint>,
81 pub stroke_width: Option<Animatable<f32>>,
82 pub opacity: Option<Animatable<f32>>,
83 #[serde(deserialize_with = "de_fill_rule")]
84 pub fill_rule: Option<FillRuleKind>,
85 #[serde(deserialize_with = "de_linecap")]
86 pub stroke_linecap: Option<LinecapKind>,
87 #[serde(deserialize_with = "de_linejoin")]
88 pub stroke_linejoin: Option<LinejoinKind>,
89
90 #[serde(deserialize_with = "de_transform")]
92 pub transform: Option<ShapeTransform>,
93
94 #[serde(deserialize_with = "de_transition")]
105 pub transition: Option<Box<ShapeTransitionSpec>>,
106}
107
108pub(crate) type NumericAttrAccessor = fn(&ShapeAttrs) -> &Option<Animatable<f32>>;
111
112pub(crate) type NumericAttrAccessorMut = fn(&mut ShapeAttrs) -> &mut Option<Animatable<f32>>;
115
116pub(crate) const NUMERIC_ATTR_COUNT: usize = 15;
125pub(crate) const NUMERIC_ATTRS: [(&str, NumericAttrAccessor, NumericAttrAccessorMut);
126 NUMERIC_ATTR_COUNT] = [
127 ("x", |a| &a.x, |a| &mut a.x),
128 ("y", |a| &a.y, |a| &mut a.y),
129 ("width", |a| &a.width, |a| &mut a.width),
130 ("height", |a| &a.height, |a| &mut a.height),
131 ("cx", |a| &a.cx, |a| &mut a.cx),
132 ("cy", |a| &a.cy, |a| &mut a.cy),
133 ("r", |a| &a.r, |a| &mut a.r),
134 ("rx", |a| &a.rx, |a| &mut a.rx),
135 ("ry", |a| &a.ry, |a| &mut a.ry),
136 ("x1", |a| &a.x1, |a| &mut a.x1),
137 ("y1", |a| &a.y1, |a| &mut a.y1),
138 ("x2", |a| &a.x2, |a| &mut a.x2),
139 ("y2", |a| &a.y2, |a| &mut a.y2),
140 ("strokeWidth", |a| &a.stroke_width, |a| &mut a.stroke_width),
141 ("opacity", |a| &a.opacity, |a| &mut a.opacity),
142];
143
144pub(crate) fn numeric_attr_mut<'a>(
148 attrs: &'a mut ShapeAttrs,
149 name: &str,
150) -> Option<&'a mut Option<Animatable<f32>>> {
151 NUMERIC_ATTRS
152 .iter()
153 .find(|(n, _, _)| *n == name)
154 .map(|(_, _, m)| m(attrs))
155}
156
157pub(crate) fn numeric_attr<'a>(
161 attrs: &'a ShapeAttrs,
162 name: &str,
163) -> Option<&'a Option<Animatable<f32>>> {
164 NUMERIC_ATTRS
165 .iter()
166 .find(|(n, _, _)| *n == name)
167 .map(|(_, r, _)| r(attrs))
168}
169
170#[cfg(test)]
173pub(crate) fn st(v: f32) -> Option<Animatable<f32>> {
174 Some(Animatable::Static(v))
175}
176
177#[derive(Debug, Clone, Default, PartialEq)]
184pub struct ShapeTransitionSpec {
185 entries: [Option<crate::transition::ChannelTransition>; NUMERIC_ATTR_COUNT],
186}
187
188impl ShapeTransitionSpec {
189 pub fn for_attr(&self, name: &str) -> Option<&crate::transition::ChannelTransition> {
192 NUMERIC_ATTRS
193 .iter()
194 .position(|(n, _, _)| *n == name)
195 .and_then(|i| self.entries[i].as_ref())
196 }
197
198 pub(crate) fn at(&self, index: usize) -> Option<&crate::transition::ChannelTransition> {
201 self.entries[index].as_ref()
202 }
203}
204
205fn de_transition<'de, D: Deserializer<'de>>(
213 d: D,
214) -> Result<Option<Box<ShapeTransitionSpec>>, D::Error> {
215 let Some(value) = Option::<serde_json::Value>::deserialize(d)? else {
216 return Ok(None);
217 };
218 let serde_json::Value::Object(map) = value else {
219 if !value.is_null() {
220 decode_warn(
221 "shapeTransition",
222 &value.to_string(),
223 "transition takes an object of per-attr timing specs; dropping",
224 );
225 }
226 return Ok(None);
227 };
228 let mut spec = ShapeTransitionSpec::default();
229 for (key, entry) in map {
230 let Some(i) = NUMERIC_ATTRS.iter().position(|(n, _, _)| *n == key) else {
231 decode_warn(
232 "shapeTransition",
233 &key,
234 &format!("`{key}` is not a numeric shape attr (only those ease); dropping"),
235 );
236 continue;
237 };
238 match serde_json::from_value(entry) {
239 Ok(timing) => spec.entries[i] = Some(timing),
240 Err(e) => {
241 decode_warn(
242 "shapeTransition",
243 &key,
244 &format!("invalid transition spec for `{key}`: {e}; dropping"),
245 );
246 }
247 }
248 }
249 Ok(Some(Box::new(spec)))
250}
251
252#[derive(Debug, Clone, Copy, PartialEq)]
256pub enum ShapePaint {
257 None,
258 Color(Srgba),
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum FillRuleKind {
264 NonZero,
265 EvenOdd,
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub enum LinecapKind {
271 Butt,
272 Round,
273 Square,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub enum LinejoinKind {
279 Miter,
280 Round,
281 Bevel,
282}
283
284#[derive(Debug, Clone, Copy, PartialEq)]
296pub struct ShapeTransform(pub [f32; 6]);
297
298impl Default for ShapeTransform {
299 fn default() -> Self {
300 ShapeTransform([1.0, 0.0, 0.0, 1.0, 0.0, 0.0])
301 }
302}
303
304const IDENTITY: [f64; 6] = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0];
305
306fn mul(a: [f64; 6], b: [f64; 6]) -> [f64; 6] {
309 [
310 a[0] * b[0] + a[2] * b[1],
311 a[1] * b[0] + a[3] * b[1],
312 a[0] * b[2] + a[2] * b[3],
313 a[1] * b[2] + a[3] * b[3],
314 a[0] * b[4] + a[2] * b[5] + a[4],
315 a[1] * b[4] + a[3] * b[5] + a[5],
316 ]
317}
318
319impl ShapeTransform {
320 pub(crate) fn parse(s: &str) -> Result<ShapeTransform, String> {
324 use svgtypes::{TransformListParser, TransformListToken as T};
325 let mut m = IDENTITY;
326 for token in TransformListParser::from(s) {
327 let token = token.map_err(|e| format!("invalid transform {s:?}: {e}"))?;
328 let t = match token {
329 T::Translate { tx, ty } => [1.0, 0.0, 0.0, 1.0, tx, ty],
330 T::Scale { sx, sy } => [sx, 0.0, 0.0, sy, 0.0, 0.0],
331 T::Rotate { angle } => {
332 let (sin, cos) = angle.to_radians().sin_cos();
333 [cos, sin, -sin, cos, 0.0, 0.0]
334 }
335 T::Matrix { .. } | T::SkewX { .. } | T::SkewY { .. } => {
336 return Err(format!(
337 "unsupported transform function in {s:?} \
338 (v1 supports translate/scale/rotate)"
339 ));
340 }
341 };
342 m = mul(m, t);
343 }
344 Ok(ShapeTransform(m.map(|v| v as f32)))
345 }
346}
347
348#[derive(Debug, Clone, Copy, PartialEq)]
351pub struct ViewBox {
352 pub min: Vec2,
353 pub size: Vec2,
354}
355
356impl ViewBox {
357 pub(crate) fn parse(s: &str) -> Result<ViewBox, String> {
360 let vb: svgtypes::ViewBox = s
361 .parse()
362 .map_err(|e| format!("invalid viewBox {s:?}: {e}"))?;
363 Ok(ViewBox {
364 min: Vec2::new(vb.x as f32, vb.y as f32),
365 size: Vec2::new(vb.w as f32, vb.h as f32),
366 })
367 }
368}
369
370pub(crate) fn de_view_box<'de, D: Deserializer<'de>>(d: D) -> Result<Option<ViewBox>, D::Error> {
374 struct V;
375 impl<'de> Visitor<'de> for V {
376 type Value = Option<ViewBox>;
377 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
378 f.write_str("a viewBox string \"minX minY width height\"")
379 }
380 fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
381 Ok(match ViewBox::parse(s) {
382 Ok(vb) => Some(vb),
383 Err(e) => {
384 decode_warn("viewBox", s, &e);
385 None
386 }
387 })
388 }
389 fn visit_map<A: de::MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
390 warn_object_dropped(map, "viewBox").map(|()| None)
391 }
392 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
393 Ok(None)
394 }
395 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
396 Ok(None)
397 }
398 }
399 d.deserialize_any(V)
400}
401
402fn warn_object_dropped<'de, A: de::MapAccess<'de>>(
408 map: A,
409 kind: &'static str,
410) -> Result<(), A::Error> {
411 let v = serde_json::Value::deserialize(de::value::MapAccessDeserializer::new(map))?;
412 let hint = if v.get("animated").is_some() {
413 " (only numeric shape attrs accept { animated } bindings)"
414 } else {
415 ""
416 };
417 decode_warn(
418 kind,
419 &v.to_string(),
420 &format!("unexpected object value{hint}; dropping"),
421 );
422 Ok(())
423}
424
425fn de_path<'de, D: Deserializer<'de>>(d: D) -> Result<Option<PathData>, D::Error> {
426 struct V;
427 impl<'de> Visitor<'de> for V {
428 type Value = Option<PathData>;
429 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
430 f.write_str("an SVG path data string")
431 }
432 fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
433 Ok(match PathData::parse(s) {
434 Ok(p) => Some(p),
435 Err(e) => {
436 decode_warn("shapePath", s, &e);
437 None
438 }
439 })
440 }
441 fn visit_map<A: de::MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
442 warn_object_dropped(map, "shapePath").map(|()| None)
443 }
444 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
445 Ok(None)
446 }
447 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
448 Ok(None)
449 }
450 }
451 d.deserialize_any(V)
452}
453
454fn de_points<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<Vec2>>, D::Error> {
455 struct V;
456 impl<'de> Visitor<'de> for V {
457 type Value = Option<Vec<Vec2>>;
458 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
459 f.write_str("a flat number array [x0, y0, x1, y1, …]")
460 }
461 fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
462 let mut nums = Vec::with_capacity(seq.size_hint().unwrap_or(0));
463 while let Some(n) = seq.next_element::<f32>()? {
464 nums.push(n);
465 }
466 if nums.len() % 2 != 0 {
467 decode_warn(
468 "shapePoints",
469 &format!("[{} numbers]", nums.len()),
470 &format!(
471 "points needs an even number of coordinates, got {}; dropping",
472 nums.len()
473 ),
474 );
475 return Ok(None);
476 }
477 Ok(Some(
478 nums.chunks_exact(2)
479 .map(|p| Vec2::new(p[0], p[1]))
480 .collect(),
481 ))
482 }
483 fn visit_map<A: de::MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
484 warn_object_dropped(map, "shapePoints").map(|()| None)
485 }
486 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
487 Ok(None)
488 }
489 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
490 Ok(None)
491 }
492 }
493 d.deserialize_any(V)
494}
495
496fn de_paint<'de, D: Deserializer<'de>>(d: D) -> Result<Option<ShapePaint>, D::Error> {
497 struct V;
498 impl<'de> Visitor<'de> for V {
499 type Value = Option<ShapePaint>;
500 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
501 f.write_str("a CSS color string or the keyword \"none\"")
502 }
503 fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
504 if s == "none" {
505 return Ok(Some(ShapePaint::None));
506 }
507 Ok(match parse_css_color(s) {
508 Some(c) => Some(ShapePaint::Color(c)),
509 None => {
510 decode_warn("shapePaint", s, &format!("unrecognized paint {s:?}"));
511 None
512 }
513 })
514 }
515 fn visit_map<A: de::MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
516 warn_object_dropped(map, "shapePaint").map(|()| None)
517 }
518 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
519 Ok(None)
520 }
521 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
522 Ok(None)
523 }
524 }
525 d.deserialize_any(V)
526}
527
528fn de_transform<'de, D: Deserializer<'de>>(d: D) -> Result<Option<ShapeTransform>, D::Error> {
529 struct V;
530 impl<'de> Visitor<'de> for V {
531 type Value = Option<ShapeTransform>;
532 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
533 f.write_str("an SVG transform list string")
534 }
535 fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
536 Ok(match ShapeTransform::parse(s) {
537 Ok(t) => Some(t),
538 Err(e) => {
539 decode_warn("shapeTransform", s, &e);
540 None
541 }
542 })
543 }
544 fn visit_map<A: de::MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
545 warn_object_dropped(map, "shapeTransform").map(|()| None)
546 }
547 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
548 Ok(None)
549 }
550 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
551 Ok(None)
552 }
553 }
554 d.deserialize_any(V)
555}
556
557macro_rules! shape_keywords {
562 ($( fn $fn_name:ident($ty:ident) { $($kw:literal => $variant:ident),+ $(,)? } )+) => { $(
563 fn $fn_name<'de, D: Deserializer<'de>>(d: D) -> Result<Option<$ty>, D::Error> {
564 struct V;
565 impl<'de> Visitor<'de> for V {
566 type Value = Option<$ty>;
567 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
568 f.write_str(concat!("a `", stringify!($ty), "` keyword"))
569 }
570 fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
571 Ok(match s {
572 $( $kw => Some(<$ty>::$variant), )+
573 _ => {
574 decode_warn(
575 "shapeEnum",
576 s,
577 &format!(
578 concat!("unrecognized ", stringify!($ty), " keyword {:?}"),
579 s
580 ),
581 );
582 None
583 }
584 })
585 }
586 fn visit_map<A: de::MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
587 warn_object_dropped(map, "shapeEnum").map(|()| None)
588 }
589 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
590 Ok(None)
591 }
592 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
593 Ok(None)
594 }
595 }
596 d.deserialize_any(V)
597 }
598 )+ };
599}
600
601shape_keywords! {
602 fn de_fill_rule(FillRuleKind) {
603 "nonzero" => NonZero, "evenodd" => EvenOdd,
604 }
605 fn de_linecap(LinecapKind) {
606 "butt" => Butt, "round" => Round, "square" => Square,
607 }
608 fn de_linejoin(LinejoinKind) {
609 "miter" => Miter, "round" => Round, "bevel" => Bevel,
610 }
611}