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