use std::collections::HashMap;
use std::num::NonZeroU32;
use std::str::FromStr;
#[rustfmt::skip] mod names;
mod parse;
mod text;
pub use names::{AId, EId};
pub struct Document<'input> {
nodes: Vec<NodeData>,
attrs: Vec<Attribute<'input>>,
links: HashMap<String, NodeId>,
}
impl<'input> Document<'input> {
#[inline]
pub fn root<'a>(&'a self) -> SvgNode<'a, 'input> {
SvgNode {
id: NodeId::new(0),
d: &self.nodes[0],
doc: self,
}
}
#[inline]
pub fn root_element<'a>(&'a self) -> SvgNode<'a, 'input> {
self.root().first_element_child().unwrap()
}
#[inline]
pub fn descendants<'a>(&'a self) -> Descendants<'a, 'input> {
self.root().descendants()
}
#[inline]
pub fn element_by_id<'a>(&'a self, id: &str) -> Option<SvgNode<'a, 'input>> {
let node_id = self.links.get(id)?;
Some(self.get(*node_id))
}
#[inline]
fn get<'a>(&'a self, id: NodeId) -> SvgNode<'a, 'input> {
SvgNode {
id,
d: &self.nodes[id.get_usize()],
doc: self,
}
}
}
impl std::fmt::Debug for Document<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
if !self.root().has_children() {
return write!(f, "Document []");
}
macro_rules! writeln_indented {
($depth:expr, $f:expr, $fmt:expr) => {
for _ in 0..$depth { write!($f, " ")?; }
writeln!($f, $fmt)?;
};
($depth:expr, $f:expr, $fmt:expr, $($arg:tt)*) => {
for _ in 0..$depth { write!($f, " ")?; }
writeln!($f, $fmt, $($arg)*)?;
};
}
fn print_children(
parent: SvgNode,
depth: usize,
f: &mut std::fmt::Formatter,
) -> Result<(), std::fmt::Error> {
for child in parent.children() {
if child.is_element() {
writeln_indented!(depth, f, "Element {{");
writeln_indented!(depth, f, " tag_name: {:?}", child.tag_name());
if !child.attributes().is_empty() {
writeln_indented!(depth + 1, f, "attributes: [");
for attr in child.attributes() {
writeln_indented!(depth + 2, f, "{:?}", attr);
}
writeln_indented!(depth + 1, f, "]");
}
if child.has_children() {
writeln_indented!(depth, f, " children: [");
print_children(child, depth + 2, f)?;
writeln_indented!(depth, f, " ]");
}
writeln_indented!(depth, f, "}}");
} else {
writeln_indented!(depth, f, "{:?}", child);
}
}
Ok(())
}
writeln!(f, "Document [")?;
print_children(self.root(), 1, f)?;
writeln!(f, "]")?;
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct ShortRange {
start: u32,
end: u32,
}
impl ShortRange {
#[inline]
fn new(start: u32, end: u32) -> Self {
ShortRange { start, end }
}
#[inline]
fn to_urange(self) -> std::ops::Range<usize> {
self.start as usize..self.end as usize
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub(crate) struct NodeId(NonZeroU32);
impl NodeId {
#[inline]
fn new(id: u32) -> Self {
debug_assert!(id < core::u32::MAX);
NodeId(NonZeroU32::new(id + 1).unwrap())
}
#[inline]
fn get(self) -> u32 {
self.0.get() - 1
}
#[inline]
fn get_usize(self) -> usize {
self.get() as usize
}
}
impl From<usize> for NodeId {
#[inline]
fn from(id: usize) -> Self {
debug_assert!(id <= core::u32::MAX as usize);
NodeId::new(id as u32)
}
}
pub(crate) enum NodeKind {
Root,
Element {
tag_name: EId,
attributes: ShortRange,
},
Text(String),
}
struct NodeData {
parent: Option<NodeId>,
next_sibling: Option<NodeId>,
children: Option<(NodeId, NodeId)>,
kind: NodeKind,
}
#[derive(Clone)]
pub struct Attribute<'input> {
pub name: AId,
pub value: roxmltree::StringStorage<'input>,
}
impl std::fmt::Debug for Attribute<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"Attribute {{ name: {:?}, value: {} }}",
self.name, self.value
)
}
}
#[derive(Clone, Copy)]
pub struct SvgNode<'a, 'input: 'a> {
id: NodeId,
doc: &'a Document<'input>,
d: &'a NodeData,
}
impl Eq for SvgNode<'_, '_> {}
impl PartialEq for SvgNode<'_, '_> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id == other.id && std::ptr::eq(self.doc, other.doc) && std::ptr::eq(self.d, other.d)
}
}
impl<'a, 'input: 'a> SvgNode<'a, 'input> {
#[inline]
fn id(&self) -> NodeId {
self.id
}
#[inline]
pub fn is_element(&self) -> bool {
matches!(self.d.kind, NodeKind::Element { .. })
}
#[inline]
pub fn is_text(&self) -> bool {
matches!(self.d.kind, NodeKind::Text(_))
}
#[inline]
pub fn document(&self) -> &'a Document<'input> {
self.doc
}
#[inline]
pub fn tag_name(&self) -> Option<EId> {
match self.d.kind {
NodeKind::Element { tag_name, .. } => Some(tag_name),
_ => None,
}
}
#[inline]
pub fn element_id(&self) -> &'a str {
self.attribute(AId::Id).unwrap_or("")
}
pub fn attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {
let value = self
.attributes()
.iter()
.find(|a| a.name == aid)
.map(|a| a.value.as_str())?;
match T::parse(*self, aid, value) {
Some(v) => Some(v),
None => {
log::warn!("Failed to parse {} value: '{}'.", aid, value);
None
}
}
}
pub fn try_attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {
let value = self
.attributes()
.iter()
.find(|a| a.name == aid)
.map(|a| a.value.as_str())?;
T::parse(*self, aid, value)
}
#[inline]
fn node_attribute(&self, aid: AId) -> Option<SvgNode<'a, 'input>> {
let value = self.attribute(aid)?;
let id = if aid == AId::Href {
svgtypes::IRI::from_str(value).ok().map(|v| v.0)
} else {
svgtypes::FuncIRI::from_str(value).ok().map(|v| v.0)
}?;
self.document().element_by_id(id)
}
#[inline]
pub fn has_attribute(&self, aid: AId) -> bool {
self.attributes().iter().any(|a| a.name == aid)
}
#[inline]
pub fn attributes(&self) -> &'a [Attribute<'input>] {
match self.d.kind {
NodeKind::Element { ref attributes, .. } => &self.doc.attrs[attributes.to_urange()],
_ => &[],
}
}
#[inline]
fn attribute_id(&self, aid: AId) -> Option<usize> {
match self.d.kind {
NodeKind::Element { ref attributes, .. } => {
let idx = self.attributes().iter().position(|attr| attr.name == aid)?;
Some(attributes.start as usize + idx)
}
_ => None,
}
}
pub fn find_attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> {
self.find_attribute_impl(aid)?.attribute(aid)
}
fn find_attribute_impl(&self, aid: AId) -> Option<SvgNode<'a, 'input>> {
if aid.is_inheritable() {
for n in self.ancestors() {
if n.has_attribute(aid) {
return Some(n);
}
}
None
} else {
if self.has_attribute(aid) {
Some(*self)
} else {
let n = self.parent_element()?;
if n.has_attribute(aid) {
Some(n)
} else {
None
}
}
}
}
#[inline]
pub fn text(&self) -> &'a str {
match self.d.kind {
NodeKind::Element { .. } => match self.first_child() {
Some(child) if child.is_text() => match self.doc.nodes[child.id.get_usize()].kind {
NodeKind::Text(ref text) => text,
_ => "",
},
_ => "",
},
NodeKind::Text(ref text) => text,
_ => "",
}
}
#[inline]
pub fn parent(&self) -> Option<Self> {
self.d.parent.map(|id| self.doc.get(id))
}
#[inline]
pub fn parent_element(&self) -> Option<Self> {
self.ancestors().skip(1).find(|n| n.is_element())
}
#[inline]
pub fn next_sibling(&self) -> Option<Self> {
self.d.next_sibling.map(|id| self.doc.get(id))
}
#[inline]
pub fn first_child(&self) -> Option<Self> {
self.d.children.map(|(id, _)| self.doc.get(id))
}
#[inline]
pub fn first_element_child(&self) -> Option<Self> {
self.children().find(|n| n.is_element())
}
#[inline]
pub fn last_child(&self) -> Option<Self> {
self.d.children.map(|(_, id)| self.doc.get(id))
}
#[inline]
pub fn has_children(&self) -> bool {
self.d.children.is_some()
}
#[inline]
pub fn ancestors(&self) -> Ancestors<'a, 'input> {
Ancestors(Some(*self))
}
#[inline]
pub fn children(&self) -> Children<'a, 'input> {
Children {
front: self.first_child(),
back: self.last_child(),
}
}
#[inline]
fn traverse(&self) -> Traverse<'a, 'input> {
Traverse {
root: *self,
edge: None,
}
}
#[inline]
pub fn descendants(&self) -> Descendants<'a, 'input> {
Descendants(self.traverse())
}
#[inline]
pub fn href_iter(&self) -> HrefIter<'a, 'input> {
HrefIter {
doc: self.document(),
origin: self.id(),
curr: self.id(),
is_first: true,
is_finished: false,
}
}
}
impl std::fmt::Debug for SvgNode<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self.d.kind {
NodeKind::Root => write!(f, "Root"),
NodeKind::Element { .. } => {
write!(
f,
"Element {{ tag_name: {:?}, attributes: {:?} }}",
self.tag_name(),
self.attributes()
)
}
NodeKind::Text(ref text) => write!(f, "Text({:?})", text),
}
}
}
#[derive(Clone, Debug)]
pub struct Ancestors<'a, 'input: 'a>(Option<SvgNode<'a, 'input>>);
impl<'a, 'input: 'a> Iterator for Ancestors<'a, 'input> {
type Item = SvgNode<'a, 'input>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let node = self.0.take();
self.0 = node.as_ref().and_then(SvgNode::parent);
node
}
}
#[derive(Clone, Debug)]
pub struct Children<'a, 'input: 'a> {
front: Option<SvgNode<'a, 'input>>,
back: Option<SvgNode<'a, 'input>>,
}
impl<'a, 'input: 'a> Iterator for Children<'a, 'input> {
type Item = SvgNode<'a, 'input>;
fn next(&mut self) -> Option<Self::Item> {
let node = self.front.take();
if self.front == self.back {
self.back = None;
} else {
self.front = node.as_ref().and_then(SvgNode::next_sibling);
}
node
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum Edge<'a, 'input: 'a> {
Open(SvgNode<'a, 'input>),
Close(SvgNode<'a, 'input>),
}
#[derive(Clone, Debug)]
struct Traverse<'a, 'input: 'a> {
root: SvgNode<'a, 'input>,
edge: Option<Edge<'a, 'input>>,
}
impl<'a, 'input: 'a> Iterator for Traverse<'a, 'input> {
type Item = Edge<'a, 'input>;
fn next(&mut self) -> Option<Self::Item> {
match self.edge {
Some(Edge::Open(node)) => {
self.edge = Some(match node.first_child() {
Some(first_child) => Edge::Open(first_child),
None => Edge::Close(node),
});
}
Some(Edge::Close(node)) => {
if node == self.root {
self.edge = None;
} else if let Some(next_sibling) = node.next_sibling() {
self.edge = Some(Edge::Open(next_sibling));
} else {
self.edge = node.parent().map(Edge::Close);
}
}
None => {
self.edge = Some(Edge::Open(self.root));
}
}
self.edge
}
}
#[derive(Clone, Debug)]
pub struct Descendants<'a, 'input: 'a>(Traverse<'a, 'input>);
impl<'a, 'input: 'a> Iterator for Descendants<'a, 'input> {
type Item = SvgNode<'a, 'input>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
for edge in &mut self.0 {
if let Edge::Open(node) = edge {
return Some(node);
}
}
None
}
}
#[derive(Clone, Debug)]
pub struct HrefIter<'a, 'input: 'a> {
doc: &'a Document<'input>,
origin: NodeId,
curr: NodeId,
is_first: bool,
is_finished: bool,
}
impl<'a, 'input: 'a> Iterator for HrefIter<'a, 'input> {
type Item = SvgNode<'a, 'input>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_finished {
return None;
}
if self.is_first {
self.is_first = false;
return Some(self.doc.get(self.curr));
}
if let Some(link) = self.doc.get(self.curr).node_attribute(AId::Href) {
if link.id() == self.curr || link.id() == self.origin {
log::warn!(
"Element '#{}' cannot reference itself via 'xlink:href'.",
self.doc.get(self.origin).element_id()
);
self.is_finished = true;
return None;
}
self.curr = link.id();
Some(self.doc.get(self.curr))
} else {
None
}
}
}
impl EId {
pub fn is_graphic(&self) -> bool {
matches!(
self,
EId::Circle
| EId::Ellipse
| EId::Image
| EId::Line
| EId::Path
| EId::Polygon
| EId::Polyline
| EId::Rect
| EId::Text
| EId::Use
)
}
pub fn is_gradient(&self) -> bool {
matches!(self, EId::LinearGradient | EId::RadialGradient)
}
pub fn is_paint_server(&self) -> bool {
matches!(
self,
EId::LinearGradient | EId::RadialGradient | EId::Pattern
)
}
}
impl AId {
fn is_presentation(&self) -> bool {
matches!(
self,
AId::AlignmentBaseline
| AId::BaselineShift
| AId::ClipPath
| AId::ClipRule
| AId::Color
| AId::ColorInterpolation
| AId::ColorInterpolationFilters
| AId::ColorRendering
| AId::Direction
| AId::Display
| AId::DominantBaseline
| AId::Fill
| AId::FillOpacity
| AId::FillRule
| AId::Filter
| AId::FloodColor
| AId::FloodOpacity
| AId::FontFamily
| AId::FontKerning | AId::FontSize
| AId::FontSizeAdjust
| AId::FontStretch
| AId::FontStyle
| AId::FontVariant
| AId::FontWeight
| AId::GlyphOrientationHorizontal
| AId::GlyphOrientationVertical
| AId::ImageRendering
| AId::Isolation | AId::LetterSpacing
| AId::LightingColor
| AId::MarkerEnd
| AId::MarkerMid
| AId::MarkerStart
| AId::Mask
| AId::MaskType
| AId::MixBlendMode | AId::Opacity
| AId::Overflow
| AId::PaintOrder
| AId::ShapeRendering
| AId::StopColor
| AId::StopOpacity
| AId::Stroke
| AId::StrokeDasharray
| AId::StrokeDashoffset
| AId::StrokeLinecap
| AId::StrokeLinejoin
| AId::StrokeMiterlimit
| AId::StrokeOpacity
| AId::StrokeWidth
| AId::TextAnchor
| AId::TextDecoration
| AId::TextOverflow
| AId::TextRendering
| AId::Transform
| AId::TransformOrigin
| AId::UnicodeBidi
| AId::VectorEffect
| AId::Visibility
| AId::WhiteSpace
| AId::WordSpacing
| AId::WritingMode
)
}
fn is_inheritable(&self) -> bool {
if self.is_presentation() {
!is_non_inheritable(*self)
} else {
false
}
}
fn allows_inherit_value(&self) -> bool {
matches!(
self,
AId::AlignmentBaseline
| AId::BaselineShift
| AId::ClipPath
| AId::ClipRule
| AId::Color
| AId::ColorInterpolationFilters
| AId::Direction
| AId::Display
| AId::DominantBaseline
| AId::Fill
| AId::FillOpacity
| AId::FillRule
| AId::Filter
| AId::FloodColor
| AId::FloodOpacity
| AId::FontFamily
| AId::FontKerning
| AId::FontSize
| AId::FontStretch
| AId::FontStyle
| AId::FontVariant
| AId::FontWeight
| AId::ImageRendering
| AId::Kerning
| AId::LetterSpacing
| AId::MarkerEnd
| AId::MarkerMid
| AId::MarkerStart
| AId::Mask
| AId::Opacity
| AId::Overflow
| AId::ShapeRendering
| AId::StopColor
| AId::StopOpacity
| AId::Stroke
| AId::StrokeDasharray
| AId::StrokeDashoffset
| AId::StrokeLinecap
| AId::StrokeLinejoin
| AId::StrokeMiterlimit
| AId::StrokeOpacity
| AId::StrokeWidth
| AId::TextAnchor
| AId::TextDecoration
| AId::TextRendering
| AId::Visibility
| AId::WordSpacing
| AId::WritingMode
)
}
}
fn is_non_inheritable(id: AId) -> bool {
matches!(
id,
AId::AlignmentBaseline
| AId::BaselineShift
| AId::ClipPath
| AId::Display
| AId::DominantBaseline
| AId::Filter
| AId::FloodColor
| AId::FloodOpacity
| AId::Mask
| AId::Opacity
| AId::Overflow
| AId::LightingColor
| AId::StopColor
| AId::StopOpacity
| AId::TextDecoration
| AId::Transform
| AId::TransformOrigin
)
}
pub trait FromValue<'a, 'input: 'a>: Sized {
fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &'a str) -> Option<Self>;
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for &'a str {
fn parse(_: SvgNode<'a, 'input>, _: AId, value: &'a str) -> Option<Self> {
Some(value)
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for f32 {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
svgtypes::Number::from_str(value).ok().map(|v| v.0 as f32)
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Length {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
svgtypes::Length::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::Opacity {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
let length = svgtypes::Length::from_str(value).ok()?;
if length.unit == svgtypes::LengthUnit::Percent {
Some(usvg_tree::Opacity::new_clamped(
length.number as f32 / 100.0,
))
} else if length.unit == svgtypes::LengthUnit::None {
Some(usvg_tree::Opacity::new_clamped(length.number as f32))
} else {
None
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::Transform {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
let ts = match svgtypes::Transform::from_str(value) {
Ok(v) => v,
Err(_) => return None,
};
let ts = usvg_tree::Transform::from_row(
ts.a as f32,
ts.b as f32,
ts.c as f32,
ts.d as f32,
ts.e as f32,
ts.f as f32,
);
if ts.is_valid() {
Some(ts)
} else {
Some(usvg_tree::Transform::default())
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::TransformOrigin {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
Self::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::ViewBox {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
Self::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::Units {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"userSpaceOnUse" => Some(usvg_tree::Units::UserSpaceOnUse),
"objectBoundingBox" => Some(usvg_tree::Units::ObjectBoundingBox),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::AspectRatio {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
Self::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::PaintOrder {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
Self::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Color {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
Self::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Angle {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
Self::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::EnableBackground {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
Self::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Paint<'a> {
fn parse(_: SvgNode, _: AId, value: &'a str) -> Option<Self> {
Self::from_str(value).ok()
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for Vec<f32> {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
let mut list = Vec::new();
for n in svgtypes::NumberListParser::from(value) {
list.push(n.ok()? as f32);
}
Some(list)
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for Vec<svgtypes::Length> {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
let mut list = Vec::new();
for n in svgtypes::LengthListParser::from(value) {
list.push(n.ok()?);
}
Some(list)
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::Visibility {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"visible" => Some(usvg_tree::Visibility::Visible),
"hidden" => Some(usvg_tree::Visibility::Hidden),
"collapse" => Some(usvg_tree::Visibility::Collapse),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::SpreadMethod {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"pad" => Some(usvg_tree::SpreadMethod::Pad),
"reflect" => Some(usvg_tree::SpreadMethod::Reflect),
"repeat" => Some(usvg_tree::SpreadMethod::Repeat),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::ShapeRendering {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"optimizeSpeed" => Some(usvg_tree::ShapeRendering::OptimizeSpeed),
"crispEdges" => Some(usvg_tree::ShapeRendering::CrispEdges),
"auto" | "geometricPrecision" => Some(usvg_tree::ShapeRendering::GeometricPrecision),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::TextRendering {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"optimizeSpeed" => Some(usvg_tree::TextRendering::OptimizeSpeed),
"auto" | "optimizeLegibility" => Some(usvg_tree::TextRendering::OptimizeLegibility),
"geometricPrecision" => Some(usvg_tree::TextRendering::GeometricPrecision),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::ImageRendering {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"auto" | "optimizeQuality" => Some(usvg_tree::ImageRendering::OptimizeQuality),
"optimizeSpeed" => Some(usvg_tree::ImageRendering::OptimizeSpeed),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::BlendMode {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"normal" => Some(usvg_tree::BlendMode::Normal),
"multiply" => Some(usvg_tree::BlendMode::Multiply),
"screen" => Some(usvg_tree::BlendMode::Screen),
"overlay" => Some(usvg_tree::BlendMode::Overlay),
"darken" => Some(usvg_tree::BlendMode::Darken),
"lighten" => Some(usvg_tree::BlendMode::Lighten),
"color-dodge" => Some(usvg_tree::BlendMode::ColorDodge),
"color-burn" => Some(usvg_tree::BlendMode::ColorBurn),
"hard-light" => Some(usvg_tree::BlendMode::HardLight),
"soft-light" => Some(usvg_tree::BlendMode::SoftLight),
"difference" => Some(usvg_tree::BlendMode::Difference),
"exclusion" => Some(usvg_tree::BlendMode::Exclusion),
"hue" => Some(usvg_tree::BlendMode::Hue),
"saturation" => Some(usvg_tree::BlendMode::Saturation),
"color" => Some(usvg_tree::BlendMode::Color),
"luminosity" => Some(usvg_tree::BlendMode::Luminosity),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for SvgNode<'a, 'input> {
fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &str) -> Option<Self> {
let id = if aid == AId::Href {
svgtypes::IRI::from_str(value).ok().map(|v| v.0)
} else {
svgtypes::FuncIRI::from_str(value).ok().map(|v| v.0)
}?;
node.document().element_by_id(id)
}
}