1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use std::collections::HashMap;
6use std::num::NonZeroU32;
7use std::str::FromStr;
8
9#[rustfmt::skip] mod names;
10mod parse;
11mod text;
12
13use tiny_skia_path::Transform;
14
15use crate::{
16 BlendMode, ImageRendering, Opacity, ShapeRendering, SpreadMethod, TextRendering, Units,
17 Visibility,
18};
19pub use names::{AId, EId};
20
21/// An SVG tree container.
22///
23/// Contains only element and text nodes.
24/// Text nodes are present only inside the `text` element.
25pub struct Document<'input> {
26 nodes: Vec<NodeData>,
27 attrs: Vec<Attribute<'input>>,
28 links: HashMap<String, NodeId>,
29}
30
31impl<'input> Document<'input> {
32 /// Returns the root node.
33 #[inline]
34 pub fn root<'a>(&'a self) -> SvgNode<'a, 'input> {
35 SvgNode {
36 id: NodeId::new(0),
37 d: &self.nodes[0],
38 doc: self,
39 }
40 }
41
42 /// Returns the root element.
43 #[inline]
44 pub fn root_element<'a>(&'a self) -> SvgNode<'a, 'input> {
45 // `unwrap` is safe, because `Document` is guarantee to have at least one element.
46 self.root().first_element_child().unwrap()
47 }
48
49 /// Returns an iterator over document's descendant nodes.
50 ///
51 /// Shorthand for `doc.root().descendants()`.
52 #[inline]
53 pub fn descendants<'a>(&'a self) -> Descendants<'a, 'input> {
54 self.root().descendants()
55 }
56
57 /// Returns an element by ID.
58 ///
59 /// Unlike the [`Descendants`] iterator, this is just a HashMap lookup.
60 /// Meaning it's way faster.
61 #[inline]
62 pub fn element_by_id<'a>(&'a self, id: &str) -> Option<SvgNode<'a, 'input>> {
63 let node_id = self.links.get(id)?;
64 Some(self.get(*node_id))
65 }
66
67 #[inline]
68 fn get<'a>(&'a self, id: NodeId) -> SvgNode<'a, 'input> {
69 SvgNode {
70 id,
71 d: &self.nodes[id.get_usize()],
72 doc: self,
73 }
74 }
75}
76
77impl std::fmt::Debug for Document<'_> {
78 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
79 if !self.root().has_children() {
80 return write!(f, "Document []");
81 }
82
83 macro_rules! writeln_indented {
84 ($depth:expr, $f:expr, $fmt:expr) => {
85 for _ in 0..$depth { write!($f, " ")?; }
86 writeln!($f, $fmt)?;
87 };
88 ($depth:expr, $f:expr, $fmt:expr, $($arg:tt)*) => {
89 for _ in 0..$depth { write!($f, " ")?; }
90 writeln!($f, $fmt, $($arg)*)?;
91 };
92 }
93
94 fn print_children(
95 parent: SvgNode,
96 depth: usize,
97 f: &mut std::fmt::Formatter,
98 ) -> Result<(), std::fmt::Error> {
99 for child in parent.children() {
100 if child.is_element() {
101 writeln_indented!(depth, f, "Element {{");
102 writeln_indented!(depth, f, " tag_name: {:?}", child.tag_name());
103
104 if !child.attributes().is_empty() {
105 writeln_indented!(depth + 1, f, "attributes: [");
106 for attr in child.attributes() {
107 writeln_indented!(depth + 2, f, "{:?}", attr);
108 }
109 writeln_indented!(depth + 1, f, "]");
110 }
111
112 if child.has_children() {
113 writeln_indented!(depth, f, " children: [");
114 print_children(child, depth + 2, f)?;
115 writeln_indented!(depth, f, " ]");
116 }
117
118 writeln_indented!(depth, f, "}}");
119 } else {
120 writeln_indented!(depth, f, "{:?}", child);
121 }
122 }
123
124 Ok(())
125 }
126
127 writeln!(f, "Document [")?;
128 print_children(self.root(), 1, f)?;
129 writeln!(f, "]")?;
130
131 Ok(())
132 }
133}
134
135#[derive(Clone, Copy, Debug)]
136pub(crate) struct ShortRange {
137 start: u32,
138 end: u32,
139}
140
141impl ShortRange {
142 #[inline]
143 fn new(start: u32, end: u32) -> Self {
144 ShortRange { start, end }
145 }
146
147 #[inline]
148 fn to_urange(self) -> std::ops::Range<usize> {
149 self.start as usize..self.end as usize
150 }
151}
152
153#[derive(Clone, Copy, PartialEq, Debug)]
154pub(crate) struct NodeId(NonZeroU32);
155
156impl NodeId {
157 #[inline]
158 fn new(id: u32) -> Self {
159 debug_assert!(id < core::u32::MAX);
160
161 // We are using `NonZeroU32` to reduce overhead of `Option<NodeId>`.
162 NodeId(NonZeroU32::new(id + 1).unwrap())
163 }
164
165 #[inline]
166 fn get(self) -> u32 {
167 self.0.get() - 1
168 }
169
170 #[inline]
171 fn get_usize(self) -> usize {
172 self.get() as usize
173 }
174}
175
176impl From<usize> for NodeId {
177 #[inline]
178 fn from(id: usize) -> Self {
179 // We already checked that `id` is limited by u32::MAX.
180 debug_assert!(id <= core::u32::MAX as usize);
181 NodeId::new(id as u32)
182 }
183}
184
185pub(crate) enum NodeKind {
186 Root,
187 Element {
188 tag_name: EId,
189 attributes: ShortRange,
190 },
191 Text(String),
192}
193
194struct NodeData {
195 parent: Option<NodeId>,
196 next_sibling: Option<NodeId>,
197 children: Option<(NodeId, NodeId)>,
198 kind: NodeKind,
199}
200
201/// An attribute.
202#[derive(Clone)]
203pub struct Attribute<'input> {
204 /// Attribute's name.
205 pub name: AId,
206 /// Attribute's value.
207 pub value: roxmltree::StringStorage<'input>,
208}
209
210impl std::fmt::Debug for Attribute<'_> {
211 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
212 write!(
213 f,
214 "Attribute {{ name: {:?}, value: {} }}",
215 self.name, self.value
216 )
217 }
218}
219
220/// An SVG node.
221#[derive(Clone, Copy)]
222pub struct SvgNode<'a, 'input: 'a> {
223 id: NodeId,
224 doc: &'a Document<'input>,
225 d: &'a NodeData,
226}
227
228impl Eq for SvgNode<'_, '_> {}
229
230impl PartialEq for SvgNode<'_, '_> {
231 #[inline]
232 fn eq(&self, other: &Self) -> bool {
233 self.id == other.id && std::ptr::eq(self.doc, b:other.doc) && std::ptr::eq(self.d, b:other.d)
234 }
235}
236
237impl<'a, 'input: 'a> SvgNode<'a, 'input> {
238 #[inline]
239 fn id(&self) -> NodeId {
240 self.id
241 }
242
243 /// Checks if the current node is an element.
244 #[inline]
245 pub fn is_element(&self) -> bool {
246 matches!(self.d.kind, NodeKind::Element { .. })
247 }
248
249 /// Checks if the current node is a text.
250 #[inline]
251 pub fn is_text(&self) -> bool {
252 matches!(self.d.kind, NodeKind::Text(_))
253 }
254
255 /// Returns node's document.
256 #[inline]
257 pub fn document(&self) -> &'a Document<'input> {
258 self.doc
259 }
260
261 /// Returns element's tag name, unless the current node is text.
262 #[inline]
263 pub fn tag_name(&self) -> Option<EId> {
264 match self.d.kind {
265 NodeKind::Element { tag_name, .. } => Some(tag_name),
266 _ => None,
267 }
268 }
269 /// Returns element's `id` attribute value.
270 ///
271 /// Returns an empty string otherwise.
272 #[inline]
273 pub fn element_id(&self) -> &'a str {
274 self.attribute(AId::Id).unwrap_or("")
275 }
276
277 /// Returns an attribute value.
278 pub fn attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {
279 let value = self
280 .attributes()
281 .iter()
282 .find(|a| a.name == aid)
283 .map(|a| a.value.as_str())?;
284 match T::parse(*self, aid, value) {
285 Some(v) => Some(v),
286 None => {
287 // TODO: show position in XML
288 log::warn!("Failed to parse {} value: '{}'.", aid, value);
289 None
290 }
291 }
292 }
293
294 /// Returns an attribute value.
295 ///
296 /// Same as `SvgNode::attribute`, but doesn't show a warning.
297 pub fn try_attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {
298 let value = self
299 .attributes()
300 .iter()
301 .find(|a| a.name == aid)
302 .map(|a| a.value.as_str())?;
303 T::parse(*self, aid, value)
304 }
305
306 #[inline]
307 fn node_attribute(&self, aid: AId) -> Option<SvgNode<'a, 'input>> {
308 let value = self.attribute(aid)?;
309 let id = if aid == AId::Href {
310 svgtypes::IRI::from_str(value).ok().map(|v| v.0)
311 } else {
312 svgtypes::FuncIRI::from_str(value).ok().map(|v| v.0)
313 }?;
314
315 self.document().element_by_id(id)
316 }
317
318 /// Checks if an attribute is present.
319 #[inline]
320 pub fn has_attribute(&self, aid: AId) -> bool {
321 self.attributes().iter().any(|a| a.name == aid)
322 }
323
324 /// Returns a list of all element's attributes.
325 #[inline]
326 pub fn attributes(&self) -> &'a [Attribute<'input>] {
327 match self.d.kind {
328 NodeKind::Element { ref attributes, .. } => &self.doc.attrs[attributes.to_urange()],
329 _ => &[],
330 }
331 }
332
333 #[inline]
334 fn attribute_id(&self, aid: AId) -> Option<usize> {
335 match self.d.kind {
336 NodeKind::Element { ref attributes, .. } => {
337 let idx = self.attributes().iter().position(|attr| attr.name == aid)?;
338 Some(attributes.start as usize + idx)
339 }
340 _ => None,
341 }
342 }
343
344 /// Finds a [`Node`] that contains the required attribute.
345 ///
346 /// For inheritable attributes walks over ancestors until a node with
347 /// the specified attribute is found.
348 ///
349 /// For non-inheritable attributes checks only the current node and the parent one.
350 /// As per SVG spec.
351 pub fn find_attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {
352 self.find_attribute_impl(aid)?.attribute(aid)
353 }
354
355 fn find_attribute_impl(&self, aid: AId) -> Option<SvgNode<'a, 'input>> {
356 if aid.is_inheritable() {
357 for n in self.ancestors() {
358 if n.has_attribute(aid) {
359 return Some(n);
360 }
361 }
362
363 None
364 } else {
365 if self.has_attribute(aid) {
366 Some(*self)
367 } else {
368 // Non-inheritable attributes can inherit a value only from a direct parent.
369 let n = self.parent_element()?;
370 if n.has_attribute(aid) {
371 Some(n)
372 } else {
373 None
374 }
375 }
376 }
377 }
378
379 /// Returns node's text data.
380 ///
381 /// For text nodes returns its content. For elements returns the first child node text.
382 #[inline]
383 pub fn text(&self) -> &'a str {
384 match self.d.kind {
385 NodeKind::Element { .. } => match self.first_child() {
386 Some(child) if child.is_text() => match self.doc.nodes[child.id.get_usize()].kind {
387 NodeKind::Text(ref text) => text,
388 _ => "",
389 },
390 _ => "",
391 },
392 NodeKind::Text(ref text) => text,
393 _ => "",
394 }
395 }
396
397 /// Returns a parent node.
398 #[inline]
399 pub fn parent(&self) -> Option<Self> {
400 self.d.parent.map(|id| self.doc.get(id))
401 }
402
403 /// Returns the parent element.
404 #[inline]
405 pub fn parent_element(&self) -> Option<Self> {
406 self.ancestors().skip(1).find(|n| n.is_element())
407 }
408
409 /// Returns the next sibling.
410 #[inline]
411 pub fn next_sibling(&self) -> Option<Self> {
412 self.d.next_sibling.map(|id| self.doc.get(id))
413 }
414
415 /// Returns the first child.
416 #[inline]
417 pub fn first_child(&self) -> Option<Self> {
418 self.d.children.map(|(id, _)| self.doc.get(id))
419 }
420
421 /// Returns the first child element.
422 #[inline]
423 pub fn first_element_child(&self) -> Option<Self> {
424 self.children().find(|n| n.is_element())
425 }
426
427 /// Returns the last child.
428 #[inline]
429 pub fn last_child(&self) -> Option<Self> {
430 self.d.children.map(|(_, id)| self.doc.get(id))
431 }
432
433 /// Checks if the node has child nodes.
434 #[inline]
435 pub fn has_children(&self) -> bool {
436 self.d.children.is_some()
437 }
438
439 /// Returns an iterator over ancestor nodes starting at this node.
440 #[inline]
441 pub fn ancestors(&self) -> Ancestors<'a, 'input> {
442 Ancestors(Some(*self))
443 }
444
445 /// Returns an iterator over children nodes.
446 #[inline]
447 pub fn children(&self) -> Children<'a, 'input> {
448 Children {
449 front: self.first_child(),
450 back: self.last_child(),
451 }
452 }
453
454 /// Returns an iterator which traverses the subtree starting at this node.
455 #[inline]
456 fn traverse(&self) -> Traverse<'a, 'input> {
457 Traverse {
458 root: *self,
459 edge: None,
460 }
461 }
462
463 /// Returns an iterator over this node and its descendants.
464 #[inline]
465 pub fn descendants(&self) -> Descendants<'a, 'input> {
466 Descendants(self.traverse())
467 }
468
469 /// Returns an iterator over elements linked via `xlink:href`.
470 #[inline]
471 pub fn href_iter(&self) -> HrefIter<'a, 'input> {
472 HrefIter {
473 doc: self.document(),
474 origin: self.id(),
475 curr: self.id(),
476 is_first: true,
477 is_finished: false,
478 }
479 }
480}
481
482impl std::fmt::Debug for SvgNode<'_, '_> {
483 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
484 match self.d.kind {
485 NodeKind::Root => write!(f, "Root"),
486 NodeKind::Element { .. } => {
487 write!(
488 f,
489 "Element {{ tag_name: {:?}, attributes: {:?} }}",
490 self.tag_name(),
491 self.attributes()
492 )
493 }
494 NodeKind::Text(ref text: &String) => write!(f, "Text({:?})", text),
495 }
496 }
497}
498
499/// An iterator over ancestor nodes.
500#[derive(Clone, Debug)]
501pub struct Ancestors<'a, 'input: 'a>(Option<SvgNode<'a, 'input>>);
502
503impl<'a, 'input: 'a> Iterator for Ancestors<'a, 'input> {
504 type Item = SvgNode<'a, 'input>;
505
506 #[inline]
507 fn next(&mut self) -> Option<Self::Item> {
508 let node: Option> = self.0.take();
509 self.0 = node.as_ref().and_then(SvgNode::parent);
510 node
511 }
512}
513
514/// An iterator over children nodes.
515#[derive(Clone, Debug)]
516pub struct Children<'a, 'input: 'a> {
517 front: Option<SvgNode<'a, 'input>>,
518 back: Option<SvgNode<'a, 'input>>,
519}
520
521impl<'a, 'input: 'a> Iterator for Children<'a, 'input> {
522 type Item = SvgNode<'a, 'input>;
523
524 fn next(&mut self) -> Option<Self::Item> {
525 let node: Option> = self.front.take();
526 if self.front == self.back {
527 self.back = None;
528 } else {
529 self.front = node.as_ref().and_then(SvgNode::next_sibling);
530 }
531 node
532 }
533}
534
535#[derive(Clone, Copy, PartialEq, Debug)]
536enum Edge<'a, 'input: 'a> {
537 Open(SvgNode<'a, 'input>),
538 Close(SvgNode<'a, 'input>),
539}
540
541#[derive(Clone, Debug)]
542struct Traverse<'a, 'input: 'a> {
543 root: SvgNode<'a, 'input>,
544 edge: Option<Edge<'a, 'input>>,
545}
546
547impl<'a, 'input: 'a> Iterator for Traverse<'a, 'input> {
548 type Item = Edge<'a, 'input>;
549
550 fn next(&mut self) -> Option<Self::Item> {
551 match self.edge {
552 Some(Edge::Open(node)) => {
553 self.edge = Some(match node.first_child() {
554 Some(first_child) => Edge::Open(first_child),
555 None => Edge::Close(node),
556 });
557 }
558 Some(Edge::Close(node)) => {
559 if node == self.root {
560 self.edge = None;
561 } else if let Some(next_sibling) = node.next_sibling() {
562 self.edge = Some(Edge::Open(next_sibling));
563 } else {
564 self.edge = node.parent().map(Edge::Close);
565 }
566 }
567 None => {
568 self.edge = Some(Edge::Open(self.root));
569 }
570 }
571
572 self.edge
573 }
574}
575
576/// A descendants iterator.
577#[derive(Clone, Debug)]
578pub struct Descendants<'a, 'input: 'a>(Traverse<'a, 'input>);
579
580impl<'a, 'input: 'a> Iterator for Descendants<'a, 'input> {
581 type Item = SvgNode<'a, 'input>;
582
583 #[inline]
584 fn next(&mut self) -> Option<Self::Item> {
585 for edge: Edge<'_, '_> in &mut self.0 {
586 if let Edge::Open(node: SvgNode<'_, '_>) = edge {
587 return Some(node);
588 }
589 }
590
591 None
592 }
593}
594
595/// An iterator over `xlink:href` references.
596#[derive(Clone, Debug)]
597pub struct HrefIter<'a, 'input: 'a> {
598 doc: &'a Document<'input>,
599 origin: NodeId,
600 curr: NodeId,
601 is_first: bool,
602 is_finished: bool,
603}
604
605impl<'a, 'input: 'a> Iterator for HrefIter<'a, 'input> {
606 type Item = SvgNode<'a, 'input>;
607
608 fn next(&mut self) -> Option<Self::Item> {
609 if self.is_finished {
610 return None;
611 }
612
613 if self.is_first {
614 self.is_first = false;
615 return Some(self.doc.get(self.curr));
616 }
617
618 if let Some(link) = self.doc.get(self.curr).node_attribute(AId::Href) {
619 if link.id() == self.curr || link.id() == self.origin {
620 log::warn!(
621 "Element '#{}' cannot reference itself via 'xlink:href'.",
622 self.doc.get(self.origin).element_id()
623 );
624 self.is_finished = true;
625 return None;
626 }
627
628 self.curr = link.id();
629 Some(self.doc.get(self.curr))
630 } else {
631 None
632 }
633 }
634}
635
636impl EId {
637 /// Checks if this is a
638 /// [graphics element](https://www.w3.org/TR/SVG11/intro.html#TermGraphicsElement).
639 pub fn is_graphic(&self) -> bool {
640 matches!(
641 self,
642 EId::Circle
643 | EId::Ellipse
644 | EId::Image
645 | EId::Line
646 | EId::Path
647 | EId::Polygon
648 | EId::Polyline
649 | EId::Rect
650 | EId::Text
651 | EId::Use
652 )
653 }
654
655 /// Checks if this is a
656 /// [gradient element](https://www.w3.org/TR/SVG11/intro.html#TermGradientElement).
657 pub fn is_gradient(&self) -> bool {
658 matches!(self, EId::LinearGradient | EId::RadialGradient)
659 }
660
661 /// Checks if this is a
662 /// [paint server element](https://www.w3.org/TR/SVG11/intro.html#TermPaint).
663 pub fn is_paint_server(&self) -> bool {
664 matches!(
665 self,
666 EId::LinearGradient | EId::RadialGradient | EId::Pattern
667 )
668 }
669}
670
671impl AId {
672 fn is_presentation(&self) -> bool {
673 matches!(
674 self,
675 AId::AlignmentBaseline
676 | AId::BaselineShift
677 | AId::ClipPath
678 | AId::ClipRule
679 | AId::Color
680 | AId::ColorInterpolation
681 | AId::ColorInterpolationFilters
682 | AId::ColorRendering
683 | AId::Direction
684 | AId::Display
685 | AId::DominantBaseline
686 | AId::Fill
687 | AId::FillOpacity
688 | AId::FillRule
689 | AId::Filter
690 | AId::FloodColor
691 | AId::FloodOpacity
692 | AId::FontFamily
693 | AId::FontKerning // technically not presentation
694 | AId::FontSize
695 | AId::FontSizeAdjust
696 | AId::FontStretch
697 | AId::FontStyle
698 | AId::FontVariant
699 | AId::FontWeight
700 | AId::GlyphOrientationHorizontal
701 | AId::GlyphOrientationVertical
702 | AId::ImageRendering
703 | AId::Isolation // technically not presentation
704 | AId::LetterSpacing
705 | AId::LightingColor
706 | AId::MarkerEnd
707 | AId::MarkerMid
708 | AId::MarkerStart
709 | AId::Mask
710 | AId::MaskType
711 | AId::MixBlendMode // technically not presentation
712 | AId::Opacity
713 | AId::Overflow
714 | AId::PaintOrder
715 | AId::ShapeRendering
716 | AId::StopColor
717 | AId::StopOpacity
718 | AId::Stroke
719 | AId::StrokeDasharray
720 | AId::StrokeDashoffset
721 | AId::StrokeLinecap
722 | AId::StrokeLinejoin
723 | AId::StrokeMiterlimit
724 | AId::StrokeOpacity
725 | AId::StrokeWidth
726 | AId::TextAnchor
727 | AId::TextDecoration
728 | AId::TextOverflow
729 | AId::TextRendering
730 | AId::Transform
731 | AId::TransformOrigin
732 | AId::UnicodeBidi
733 | AId::VectorEffect
734 | AId::Visibility
735 | AId::WhiteSpace
736 | AId::WordSpacing
737 | AId::WritingMode
738 )
739 }
740
741 /// Checks if the current attribute is inheritable.
742 fn is_inheritable(&self) -> bool {
743 if self.is_presentation() {
744 !is_non_inheritable(*self)
745 } else {
746 false
747 }
748 }
749
750 fn allows_inherit_value(&self) -> bool {
751 matches!(
752 self,
753 AId::AlignmentBaseline
754 | AId::BaselineShift
755 | AId::ClipPath
756 | AId::ClipRule
757 | AId::Color
758 | AId::ColorInterpolationFilters
759 | AId::Direction
760 | AId::Display
761 | AId::DominantBaseline
762 | AId::Fill
763 | AId::FillOpacity
764 | AId::FillRule
765 | AId::Filter
766 | AId::FloodColor
767 | AId::FloodOpacity
768 | AId::FontFamily
769 | AId::FontKerning
770 | AId::FontSize
771 | AId::FontStretch
772 | AId::FontStyle
773 | AId::FontVariant
774 | AId::FontWeight
775 | AId::ImageRendering
776 | AId::Kerning
777 | AId::LetterSpacing
778 | AId::MarkerEnd
779 | AId::MarkerMid
780 | AId::MarkerStart
781 | AId::Mask
782 | AId::Opacity
783 | AId::Overflow
784 | AId::ShapeRendering
785 | AId::StopColor
786 | AId::StopOpacity
787 | AId::Stroke
788 | AId::StrokeDasharray
789 | AId::StrokeDashoffset
790 | AId::StrokeLinecap
791 | AId::StrokeLinejoin
792 | AId::StrokeMiterlimit
793 | AId::StrokeOpacity
794 | AId::StrokeWidth
795 | AId::TextAnchor
796 | AId::TextDecoration
797 | AId::TextRendering
798 | AId::Visibility
799 | AId::WordSpacing
800 | AId::WritingMode
801 )
802 }
803}
804
805fn is_non_inheritable(id: AId) -> bool {
806 matches!(
807 id,
808 AId::AlignmentBaseline
809 | AId::BaselineShift
810 | AId::ClipPath
811 | AId::Display
812 | AId::DominantBaseline
813 | AId::Filter
814 | AId::FloodColor
815 | AId::FloodOpacity
816 | AId::Mask
817 | AId::Opacity
818 | AId::Overflow
819 | AId::LightingColor
820 | AId::StopColor
821 | AId::StopOpacity
822 | AId::TextDecoration
823 | AId::Transform
824 | AId::TransformOrigin
825 )
826}
827
828// TODO: is there a way yo make it less ugly? Too many lifetimes.
829/// A trait for parsing attribute values.
830pub trait FromValue<'a, 'input: 'a>: Sized {
831 /// Parses an attribute value.
832 ///
833 /// When `None` is returned, the attribute value will be logged as a parsing failure.
834 fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &'a str) -> Option<Self>;
835}
836
837impl<'a, 'input: 'a> FromValue<'a, 'input> for &'a str {
838 fn parse(_: SvgNode<'a, 'input>, _: AId, value: &'a str) -> Option<Self> {
839 Some(value)
840 }
841}
842
843impl<'a, 'input: 'a> FromValue<'a, 'input> for f32 {
844 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
845 svgtypes::Number::from_str(value).ok().map(|v: Number| v.0 as f32)
846 }
847}
848
849impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Length {
850 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
851 svgtypes::Length::from_str(value).ok()
852 }
853}
854
855// TODO: to svgtypes?
856impl<'a, 'input: 'a> FromValue<'a, 'input> for Opacity {
857 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
858 let length: Length = svgtypes::Length::from_str(value).ok()?;
859 if length.unit == svgtypes::LengthUnit::Percent {
860 Some(Opacity::new_clamped(length.number as f32 / 100.0))
861 } else if length.unit == svgtypes::LengthUnit::None {
862 Some(Opacity::new_clamped(length.number as f32))
863 } else {
864 None
865 }
866 }
867}
868
869impl<'a, 'input: 'a> FromValue<'a, 'input> for Transform {
870 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
871 let ts: Transform = match svgtypes::Transform::from_str(value) {
872 Ok(v: Transform) => v,
873 Err(_) => return None,
874 };
875
876 let ts: Transform = Transform::from_row(
877 sx:ts.a as f32,
878 ky:ts.b as f32,
879 kx:ts.c as f32,
880 sy:ts.d as f32,
881 tx:ts.e as f32,
882 ty:ts.f as f32,
883 );
884
885 if ts.is_valid() {
886 Some(ts)
887 } else {
888 Some(Transform::default())
889 }
890 }
891}
892
893impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::TransformOrigin {
894 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
895 Self::from_str(value).ok()
896 }
897}
898
899impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::ViewBox {
900 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
901 Self::from_str(value).ok()
902 }
903}
904
905impl<'a, 'input: 'a> FromValue<'a, 'input> for Units {
906 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
907 match value {
908 "userSpaceOnUse" => Some(Units::UserSpaceOnUse),
909 "objectBoundingBox" => Some(Units::ObjectBoundingBox),
910 _ => None,
911 }
912 }
913}
914
915impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::AspectRatio {
916 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
917 Self::from_str(value).ok()
918 }
919}
920
921impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::PaintOrder {
922 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
923 Self::from_str(value).ok()
924 }
925}
926
927impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Color {
928 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
929 Self::from_str(value).ok()
930 }
931}
932
933impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Angle {
934 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
935 Self::from_str(value).ok()
936 }
937}
938
939impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::EnableBackground {
940 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
941 Self::from_str(value).ok()
942 }
943}
944
945impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Paint<'a> {
946 fn parse(_: SvgNode, _: AId, value: &'a str) -> Option<Self> {
947 Self::from_str(text:value).ok()
948 }
949}
950
951impl<'a, 'input: 'a> FromValue<'a, 'input> for Vec<f32> {
952 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
953 let mut list: Vec = Vec::new();
954 for n: Result in svgtypes::NumberListParser::from(value) {
955 list.push(n.ok()? as f32);
956 }
957
958 Some(list)
959 }
960}
961
962impl<'a, 'input: 'a> FromValue<'a, 'input> for Vec<svgtypes::Length> {
963 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
964 let mut list: Vec = Vec::new();
965 for n: Result in svgtypes::LengthListParser::from(value) {
966 list.push(n.ok()?);
967 }
968
969 Some(list)
970 }
971}
972
973impl<'a, 'input: 'a> FromValue<'a, 'input> for Visibility {
974 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
975 match value {
976 "visible" => Some(Visibility::Visible),
977 "hidden" => Some(Visibility::Hidden),
978 "collapse" => Some(Visibility::Collapse),
979 _ => None,
980 }
981 }
982}
983
984impl<'a, 'input: 'a> FromValue<'a, 'input> for SpreadMethod {
985 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
986 match value {
987 "pad" => Some(SpreadMethod::Pad),
988 "reflect" => Some(SpreadMethod::Reflect),
989 "repeat" => Some(SpreadMethod::Repeat),
990 _ => None,
991 }
992 }
993}
994
995impl<'a, 'input: 'a> FromValue<'a, 'input> for ShapeRendering {
996 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
997 match value {
998 "optimizeSpeed" => Some(ShapeRendering::OptimizeSpeed),
999 "crispEdges" => Some(ShapeRendering::CrispEdges),
1000 "auto" | "geometricPrecision" => Some(ShapeRendering::GeometricPrecision),
1001 _ => None,
1002 }
1003 }
1004}
1005
1006impl<'a, 'input: 'a> FromValue<'a, 'input> for TextRendering {
1007 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
1008 match value {
1009 "optimizeSpeed" => Some(TextRendering::OptimizeSpeed),
1010 "auto" | "optimizeLegibility" => Some(TextRendering::OptimizeLegibility),
1011 "geometricPrecision" => Some(TextRendering::GeometricPrecision),
1012 _ => None,
1013 }
1014 }
1015}
1016
1017impl<'a, 'input: 'a> FromValue<'a, 'input> for ImageRendering {
1018 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
1019 match value {
1020 "auto" | "optimizeQuality" => Some(ImageRendering::OptimizeQuality),
1021 "optimizeSpeed" => Some(ImageRendering::OptimizeSpeed),
1022 _ => None,
1023 }
1024 }
1025}
1026
1027impl<'a, 'input: 'a> FromValue<'a, 'input> for BlendMode {
1028 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
1029 match value {
1030 "normal" => Some(BlendMode::Normal),
1031 "multiply" => Some(BlendMode::Multiply),
1032 "screen" => Some(BlendMode::Screen),
1033 "overlay" => Some(BlendMode::Overlay),
1034 "darken" => Some(BlendMode::Darken),
1035 "lighten" => Some(BlendMode::Lighten),
1036 "color-dodge" => Some(BlendMode::ColorDodge),
1037 "color-burn" => Some(BlendMode::ColorBurn),
1038 "hard-light" => Some(BlendMode::HardLight),
1039 "soft-light" => Some(BlendMode::SoftLight),
1040 "difference" => Some(BlendMode::Difference),
1041 "exclusion" => Some(BlendMode::Exclusion),
1042 "hue" => Some(BlendMode::Hue),
1043 "saturation" => Some(BlendMode::Saturation),
1044 "color" => Some(BlendMode::Color),
1045 "luminosity" => Some(BlendMode::Luminosity),
1046 _ => None,
1047 }
1048 }
1049}
1050
1051impl<'a, 'input: 'a> FromValue<'a, 'input> for SvgNode<'a, 'input> {
1052 fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &str) -> Option<Self> {
1053 let id: &str = if aid == AId::Href {
1054 svgtypes::IRI::from_str(text:value).ok().map(|v: IRI<'_>| v.0)
1055 } else {
1056 svgtypes::FuncIRI::from_str(text:value).ok().map(|v: FuncIRI<'_>| v.0)
1057 }?;
1058
1059 node.document().element_by_id(id)
1060 }
1061}
1062