1use derive_more::Deref;
7use microcad_core::{Color, Mat3, Scalar};
8use microcad_lang::{
9 model::{AttributesAccess, Model},
10 syntax::Identifier,
11 value::{Value, ValueAccess},
12};
13
14#[derive(Debug, Clone)]
15pub enum SvgTagAttribute {
16 MarkerStart(String),
18
19 MarkerEnd(String),
21
22 Style {
24 fill: Option<Color>,
25 stroke: Option<Color>,
26 stroke_width: Option<Scalar>,
27 },
28
29 Transform(Mat3),
31
32 Class(String),
34
35 Custom(String, String),
37}
38
39impl SvgTagAttribute {
40 pub fn class(s: &str) -> Self {
42 Self::Class(s.to_string())
43 }
44
45 pub fn style(fill: Option<Color>, stroke: Option<Color>, stroke_width: Option<Scalar>) -> Self {
47 Self::Style {
48 fill,
49 stroke,
50 stroke_width,
51 }
52 }
53
54 fn id(&self) -> &str {
55 match &self {
56 SvgTagAttribute::MarkerStart(_) => "marker-start",
57 SvgTagAttribute::MarkerEnd(_) => "marker-end",
58 SvgTagAttribute::Style {
59 fill: _,
60 stroke: _,
61 stroke_width: _,
62 } => "style",
63 SvgTagAttribute::Transform(_) => "transform",
64 SvgTagAttribute::Class(_) => "class",
65 SvgTagAttribute::Custom(id, _) => id,
66 }
67 }
68}
69
70impl std::fmt::Display for SvgTagAttribute {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 let value = match &self {
73 SvgTagAttribute::MarkerStart(marker_name) | SvgTagAttribute::MarkerEnd(marker_name) => {
74 format!("url(#{marker_name})")
75 }
76 SvgTagAttribute::Style {
77 fill,
78 stroke,
79 stroke_width,
80 } => format!(
81 "{fill}{stroke}{stroke_width}",
82 fill = match fill {
83 Some(fill) => format!("fill: {}; ", fill.to_svg_color()),
84 None => "fill: none; ".into(),
85 },
86 stroke = match stroke {
87 Some(stroke) => format!("stroke: {}; ", stroke.to_svg_color()),
88 None => "stroke: none; ".into(),
89 },
90 stroke_width = match stroke_width {
91 Some(stroke_width) => format!("stroke-width: {stroke_width}"),
92 None => String::new(),
93 }
94 ),
95 SvgTagAttribute::Transform(m) => {
96 let (a, b, c, d, e, f) = (m.x.x, m.x.y, m.y.x, m.y.y, m.z.x, m.z.y);
97 format!("matrix({a} {b} {c} {d} {e} {f})")
98 }
99 SvgTagAttribute::Class(class) => class.clone(),
100 SvgTagAttribute::Custom(_, value) => value.clone(),
101 };
102
103 write!(f, "{}=\"{value}\"", self.id(),)
104 }
105}
106
107#[derive(Debug, Clone, Default, Deref)]
109pub struct SvgTagAttributes(std::collections::BTreeMap<String, SvgTagAttribute>);
110
111impl SvgTagAttributes {
113 pub fn merge(mut self, mut other: Self) -> Self {
115 self.0.append(&mut other.0);
116 self
117 }
118}
119
120impl SvgTagAttributes {
122 pub fn insert(mut self, attr: SvgTagAttribute) -> Self {
124 match self.0.get_mut(attr.id()) {
125 Some(SvgTagAttribute::Class(class)) => match attr {
126 SvgTagAttribute::Class(new_class) => {
127 *class += &format!(" {new_class}");
128 }
129 _ => unreachable!(),
130 },
131 _ => {
132 self.0.insert(attr.id().to_string(), attr);
133 }
134 }
135
136 self
137 }
138
139 pub fn apply_from_model(mut self, model: &Model) -> Self {
141 if let Some(color) = model.get_color() {
142 self = self.insert(SvgTagAttribute::Style {
143 fill: Some(color),
144 stroke: None,
145 stroke_width: None,
146 });
147 }
148
149 model
150 .get_custom_attributes(&Identifier::no_ref("svg"))
151 .iter()
152 .for_each(|tuple| {
153 if let Some(Value::String(style)) = tuple.by_id(&Identifier::no_ref("style")) {
154 self = self
155 .clone()
156 .insert(SvgTagAttribute::Custom("style".into(), style.clone()));
157 }
158 if let Some(Value::String(fill)) = tuple.by_id(&Identifier::no_ref("fill")) {
159 self = self
160 .clone()
161 .insert(SvgTagAttribute::Custom("fill".into(), fill.clone()));
162 }
163 });
164 self
165 }
166}
167
168impl std::fmt::Display for SvgTagAttributes {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 write!(
171 f,
172 "{}",
173 self.0
174 .values()
175 .map(|attr| attr.to_string())
176 .collect::<Vec<_>>()
177 .join(" ")
178 )
179 }
180}
181
182impl From<SvgTagAttribute> for SvgTagAttributes {
183 fn from(value: SvgTagAttribute) -> Self {
184 [value].into_iter().collect()
185 }
186}
187
188impl FromIterator<SvgTagAttribute> for SvgTagAttributes {
189 fn from_iter<T: IntoIterator<Item = SvgTagAttribute>>(iter: T) -> Self {
190 let mut s = Self::default();
191 iter.into_iter().for_each(|attr| {
192 s.0.insert(attr.id().to_string(), attr);
193 });
194 s
195 }
196}
197
198impl<'a> FromIterator<(&'a str, &'a str)> for SvgTagAttributes {
199 fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
200 let mut s = Self::default();
201 iter.into_iter().for_each(|(key, value)| {
202 let key = key.to_string();
203 s.0.insert(key.clone(), SvgTagAttribute::Custom(key, value.to_string()));
204 });
205 s
206 }
207}