collagen/fibroblast/tags/
mod.rs

1//! This file exposes the different kinds of tags. `RootTag` and `AnyChildTag` are
2//! high-level tags; the former is the document root and the latter simply exists to
3//! wrap more specific child tags.
4//!
5//! During deserialization, objects are converted to an in-memory tag; not only is this
6//! tag's data specified by the object's key-value pairs, but in addition, the *kind* of
7//! tag to deserialize into is determined from the object's set of keys. For instance,
8//! this tag will be decoded into a plain old `<circle>`:
9//!
10//! ```json
11//! { "tag": "circle", "attrs": { ... } }
12//! ```
13//!
14//! Whereas this one will be decoded into an `<image>`, with the image at
15//! `"path/to/image"` embedded in the resulting SVG:
16//!
17//! ```json
18//! { "image_path": "path/to/image" }
19//! ```
20//!
21//! Documentation on the precise data format expected by a given kind of tag is in that
22//! tag's own documentation. Most tags accept (but don't require) the keys in
23//! [`CommonTagFields`][CommonTagFields]. Read an individual tag type's documentation
24//! for more info.
25//!
26//! At a high level, the two kinds of tags are:
27//! - [`RootTag`]: The SVG root (`<svg>...</svg>`). Contains all other child tags. There
28//!   is exactly one of these per skeleton, and it's the top level object in
29//!   `collagen.json`.
30//! - [`AnyChildTag`]: An enum wrapping any one of a number of distinct kinds of child
31//!   tags. See its docs for more info.
32
33pub(crate) mod any_child_tag;
34pub(crate) mod container_tag;
35pub(crate) mod element;
36pub(crate) mod font_tag;
37pub(crate) mod generic_tag;
38pub(crate) mod image_tag;
39pub(crate) mod nested_svg_tag;
40pub mod root_tag;
41pub(crate) mod text_tag;
42pub(crate) mod validation;
43
44use self::element::XmlAttrs;
45use crate::from_json::decoding_error::{InvalidSchemaError, InvalidSchemaErrorList};
46pub(super) use crate::{
47	fibroblast::data_types::DecodingContext, to_svg::svg_writable::ClgnDecodingResult,
48};
49pub use any_child_tag::AnyChildTag;
50use any_child_tag::UnvalidatedAnyChildTag;
51pub use container_tag::ContainerTag;
52pub use font_tag::FontTag;
53pub use generic_tag::GenericTag;
54pub use image_tag::ImageTag;
55pub use nested_svg_tag::NestedSvgTag;
56use serde::{Deserialize, Serialize};
57use std::{fmt, sync::LazyLock};
58use validation::Validatable;
59
60// The `BTreeMap` equivalent of `&[]`, which sadly only exists for `Vec`. Since
61// `BTreeMap` doesn't allocate until it has at least one element, this really costs
62// almost nothing
63pub(crate) static EMPTY_ATTRS: LazyLock<XmlAttrs> = LazyLock::new(|| XmlAttrs(Vec::new()));
64
65/// A catch-all for extra, unexpected keys
66#[derive(Serialize, Deserialize)]
67#[serde(transparent)]
68pub struct Extras(serde_json::Map<String, serde_json::Value>);
69
70impl fmt::Debug for Extras {
71	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72		write!(f, "{:?}", self.0)
73	}
74}
75
76impl Extras {
77	pub(crate) fn map(&self) -> &serde_json::Map<String, serde_json::Value> {
78		&self.0
79	}
80
81	pub(crate) fn ensure_empty(&self, for_tag: &'static str) -> Result<(), InvalidSchemaError> {
82		if !self.0.is_empty() {
83			return Err(InvalidSchemaError::unexpected_keys(
84				for_tag,
85				self.0.keys().cloned().collect(),
86			));
87		}
88
89		Ok(())
90	}
91}
92
93/// Description: A dictionary whose keys and values will be used to construct the list
94/// of `name="value"` XML attributes. For instance, `{ "tag": "circle", "attrs": { "cx":
95/// 10, "cy": 20, "r": 5 } }` will be turned into `<circle cx=10 cy=20 r=5></circle>`.
96/// Variable substitution and LISP evaluation are performed on the values in `attrs`
97/// using `vars`.
98#[derive(Debug, Default, Clone, Serialize, Deserialize)]
99#[serde(transparent)]
100pub struct DeXmlAttrs {
101	/// (Optional) A dictionary of name="value" XML attributes. None is equivalent to no
102	/// attributes.
103	#[serde(skip_serializing_if = "Option::is_none")]
104	attrs: Option<XmlAttrs>,
105}
106
107impl AsRef<XmlAttrs> for DeXmlAttrs {
108	fn as_ref(&self) -> &XmlAttrs {
109		self.attrs.as_ref().unwrap_or(&EMPTY_ATTRS)
110	}
111}
112
113/// A list of children of this tag. Each child in the list is an object interpretable as
114/// `AnyChildTag`. For example, the `children` in `{ "tag": "g", "children": [{ "tag":
115/// "rect", "attrs": ... }, { "image_path": ... }] }`
116#[derive(Debug, Clone, Serialize)]
117#[serde(transparent)]
118pub(crate) struct DeChildTags {
119	#[serde(skip_serializing_if = "Option::is_none")]
120	pub(crate) children: Option<Vec<AnyChildTag>>,
121}
122
123impl AsRef<[AnyChildTag]> for DeChildTags {
124	fn as_ref(&self) -> &[AnyChildTag] {
125		self.children.as_deref().unwrap_or(&[])
126	}
127}
128
129// Exists to take either a list of children or a sole child (which is wrapped in a
130// one-element list) without writing deserialization code by hand
131#[derive(Deserialize)]
132#[serde(untagged)]
133enum UnvalidatedDeChildTagsWrapper {
134	One(UnvalidatedAnyChildTag),
135	Multiple(Vec<UnvalidatedAnyChildTag>),
136}
137
138#[derive(Debug, Deserialize)]
139#[serde(from = "Option<UnvalidatedDeChildTagsWrapper>")]
140pub(crate) struct UnvalidatedDeChildTags {
141	#[serde(default)]
142	pub(crate) children: Option<Vec<UnvalidatedAnyChildTag>>,
143}
144
145impl From<Option<UnvalidatedDeChildTagsWrapper>> for UnvalidatedDeChildTags {
146	fn from(children: Option<UnvalidatedDeChildTagsWrapper>) -> Self {
147		Self {
148			children: children.map(|children| match children {
149				UnvalidatedDeChildTagsWrapper::One(one) => vec![one],
150				UnvalidatedDeChildTagsWrapper::Multiple(multiple) => multiple,
151			}),
152		}
153	}
154}
155
156impl Validatable for UnvalidatedDeChildTags {
157	type Validated = DeChildTags;
158
159	fn into_validated(self, errors: &mut InvalidSchemaErrorList) -> Result<DeChildTags, ()> {
160		Ok(DeChildTags {
161			children: self
162				.children
163				.map(|c| {
164					c.into_iter()
165						.map(|child| child.into_validated(errors))
166						.collect::<Result<Vec<_>, _>>()
167				}) // Option<Result<Vec<T>, E>>
168				.transpose()?,
169		})
170	}
171}