1use std::{borrow::Borrow, collections::BTreeMap, rc::Rc};
2
3use lightningcss::{declaration, printer, properties, rules, stylesheet, traits::ToCss, values};
4use markup5ever::local_name;
5use oxvg_selectors::{Element, Selector};
6use rcdom::NodeData;
7
8#[derive(Debug)]
9pub enum SVGPaint {
10 Url,
11 Color(values::color::CssColor),
12 ContextFill,
13 ContextStroke,
14 None,
15}
16
17#[derive(Debug)]
19pub enum SVGStyle {
20 MarkerMid(Option<String>),
21 Stroke(SVGPaint),
22 StrokeLinecap(properties::svg::StrokeLinecap),
23 StrokeLinejoin(properties::svg::StrokeLinejoin),
24 Unsupported,
26}
27
28#[derive(Ord, Eq, PartialEq, PartialOrd, Debug)]
29pub enum SVGStyleID {
30 MarkerMid,
31 Stroke,
32 SrokeLinecap,
33 StrokeLinejoin,
34 Unsupported,
35}
36
37#[derive(Default, Debug)]
38pub struct ComputedStyles {
39 pub inherited: BTreeMap<SVGStyleID, SVGStyle>,
40 pub declarations: BTreeMap<SVGStyleID, (u32, SVGStyle)>,
41 pub inline: BTreeMap<SVGStyleID, SVGStyle>,
42 pub important_declarations: BTreeMap<SVGStyleID, (u32, SVGStyle)>,
43 pub inline_important: BTreeMap<SVGStyleID, SVGStyle>,
44}
45
46pub fn root_style(root: &Element) -> String {
51 root.select("style")
52 .expect("`style` should be a valid selector")
53 .map(|s| {
54 s.text_content()
55 .map(|s| s.borrow().to_string())
56 .collect::<Vec<_>>()
57 .join("\n")
58 })
59 .collect::<Vec<_>>()
60 .join("\n")
61}
62
63impl ComputedStyles {
64 pub fn with_all(&mut self, node: &Rc<rcdom::Node>, styles: &[rules::CssRule]) {
65 self.with_inherited(node, styles);
66 self.with_style(node, styles);
67 self.with_attribute(node);
68 self.with_inline_style(node);
69 }
70
71 pub fn with_inherited(&mut self, node: &Rc<rcdom::Node>, styles: &[rules::CssRule]) {
72 let element = Element::new(node.clone());
73 let Some(parent) = element.get_parent() else {
74 return;
75 };
76 let mut inherited = ComputedStyles::default();
77 inherited.with_all(&parent.node, styles);
78 self.inherited = inherited.into_computed();
79 }
80
81 pub fn with_style(&mut self, node: &Rc<rcdom::Node>, styles: &[rules::CssRule]) {
82 styles
83 .iter()
84 .for_each(|s| self.with_nested_style(node, s, "", 0));
85 }
86
87 fn with_nested_style(
88 &mut self,
89 node: &Rc<rcdom::Node>,
90 style: &rules::CssRule,
91 selector: &str,
92 specificity: u32,
93 ) {
94 match style {
95 rules::CssRule::Style(r) => r.selectors.0.iter().for_each(|s| {
96 let Ok(this_selector) = s.to_css_string(printer::PrinterOptions::default()) else {
97 return;
98 };
99 let selector = format!("{selector}{this_selector}");
100 let Ok(select) = Selector::try_from(selector.as_str()) else {
101 return;
102 };
103 let element = Element::new(node.clone());
104 if !select.matches_naive(&element) {
105 return;
106 };
107 let specificity = specificity + s.specificity();
108 self.add_declarations(&r.declarations, specificity);
109 }),
110 rules::CssRule::Container(rules::container::ContainerRule { rules, .. })
111 | rules::CssRule::Media(rules::media::MediaRule { rules, .. }) => {
112 rules
113 .0
114 .iter()
115 .for_each(|r| self.with_nested_style(node, r, selector, specificity));
116 }
117 _ => {}
118 }
119 }
120
121 fn with_attribute(&mut self, node: &Rc<rcdom::Node>) {
122 let node: &rcdom::Node = node.borrow();
123 let NodeData::Element { ref attrs, .. } = node.data else {
124 return;
125 };
126 attrs.borrow().iter().for_each(|a| {
127 let name = &a.name.local;
128 let value = &a.value;
129 let style = format!("{name}:{value}");
130 let Ok(style) =
131 stylesheet::StyleAttribute::parse(&style, stylesheet::ParserOptions::default())
132 else {
133 return;
134 };
135 let property = &style.declarations.declarations[0];
136 let style = SVGStyle::from(property);
137 self.inline.insert(style.id(), style);
138 });
139 }
140
141 pub fn with_inline_style(&mut self, node: &Rc<rcdom::Node>) {
142 let element = Element::new(node.clone());
143 let Some(style) = element.get_attr(&local_name!("style")) else {
144 return;
145 };
146 let Some(style) =
147 stylesheet::StyleAttribute::parse(&style.value, stylesheet::ParserOptions::default())
148 .ok()
149 else {
150 return;
151 };
152 style
153 .declarations
154 .declarations
155 .iter()
156 .map(SVGStyle::from)
157 .for_each(|s| {
158 self.inline.insert(s.id(), s);
159 });
160 style
161 .declarations
162 .important_declarations
163 .iter()
164 .map(SVGStyle::from)
165 .for_each(|s| {
166 self.inline_important.insert(s.id(), s);
167 });
168 }
169
170 pub fn computed<'a>(&'a self) -> BTreeMap<SVGStyleID, &'a SVGStyle> {
171 let mut result = BTreeMap::new();
172 let map = |s: &'a (u32, SVGStyle)| &s.1;
173 let mut insert = |s: &'a SVGStyle| {
174 result.insert(s.id(), s);
175 };
176 self.declarations.values().map(map).for_each(&mut insert);
177 self.inline.values().for_each(&mut insert);
178 self.important_declarations
179 .values()
180 .map(map)
181 .for_each(&mut insert);
182 self.inline_important.values().for_each(insert);
183 result
184 }
185
186 pub fn into_computed(self) -> BTreeMap<SVGStyleID, SVGStyle> {
187 let mut result = BTreeMap::new();
188 let map = |s: (u32, SVGStyle)| s.1;
189 let mut insert = |s: SVGStyle| {
190 result.insert(s.id(), s);
191 };
192 self.declarations
193 .into_values()
194 .map(map)
195 .for_each(&mut insert);
196 self.inline.into_values().for_each(&mut insert);
197 self.important_declarations
198 .into_values()
199 .map(map)
200 .for_each(&mut insert);
201 self.inline_important.into_values().for_each(insert);
202 result
203 }
204
205 fn add_declarations(&mut self, declarations: &declaration::DeclarationBlock, specificity: u32) {
206 Self::set_declarations(
207 &mut self.important_declarations,
208 &declarations.important_declarations,
209 specificity,
210 );
211 Self::set_declarations(
212 &mut self.declarations,
213 &declarations.declarations,
214 specificity,
215 );
216 }
217
218 fn set_declarations(
219 record: &mut BTreeMap<SVGStyleID, (u32, SVGStyle)>,
220 declarations: &[properties::Property],
221 specificity: u32,
222 ) {
223 for d in declarations {
224 let decl = SVGStyle::from(d);
225 let id = decl.id();
226 record.insert(id, (specificity, decl));
227 }
228 }
229}
230
231impl SVGStyle {
232 fn id(&self) -> SVGStyleID {
233 match self {
234 Self::MarkerMid(_) => SVGStyleID::MarkerMid,
235 Self::Stroke(_) => SVGStyleID::Stroke,
236 Self::StrokeLinecap(_) => SVGStyleID::SrokeLinecap,
237 Self::StrokeLinejoin(_) => SVGStyleID::StrokeLinejoin,
238 Self::Unsupported => SVGStyleID::Unsupported,
239 }
240 }
241}
242
243impl From<&properties::Property<'_>> for SVGStyle {
244 fn from(value: &properties::Property) -> Self {
245 match value {
246 properties::Property::MarkerMid(m) => SVGStyle::MarkerMid(match m {
247 lightningcss::properties::svg::Marker::Url(u) => Some(u.url.to_string()),
248 lightningcss::properties::svg::Marker::None => None,
249 }),
250 properties::Property::Stroke(s) => SVGStyle::from(s),
251 properties::Property::StrokeLinecap(s) => SVGStyle::StrokeLinecap(*s),
252 properties::Property::StrokeLinejoin(s) => SVGStyle::StrokeLinejoin(*s),
253 _ => SVGStyle::Unsupported,
254 }
255 }
256}
257
258impl From<&lightningcss::properties::svg::SVGPaint<'_>> for SVGStyle {
259 fn from(value: &lightningcss::properties::svg::SVGPaint) -> Self {
260 Self::Stroke(SVGPaint::from(value))
261 }
262}
263
264impl From<&lightningcss::properties::svg::SVGPaint<'_>> for SVGPaint {
265 fn from(value: &lightningcss::properties::svg::SVGPaint<'_>) -> Self {
266 match value {
267 properties::svg::SVGPaint::Url { .. } => Self::Url,
268 properties::svg::SVGPaint::Color(c) => Self::Color(c.clone()),
269 properties::svg::SVGPaint::ContextFill => Self::ContextFill,
270 properties::svg::SVGPaint::ContextStroke => Self::ContextStroke,
271 properties::svg::SVGPaint::None => Self::None,
272 }
273 }
274}