use super::converter::{self, SvgColorExt};
use super::paint_server;
use super::svgtree::{AId, FromValue, SvgNode};
use crate::tree::ContextElement;
use crate::{
ApproxEqUlps, Color, Fill, FillRule, LineCap, LineJoin, Opacity, Paint, Stroke,
StrokeMiterlimit, Units,
};
impl<'a, 'input: 'a> FromValue<'a, 'input> for LineCap {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"butt" => Some(LineCap::Butt),
"round" => Some(LineCap::Round),
"square" => Some(LineCap::Square),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for LineJoin {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"miter" => Some(LineJoin::Miter),
"miter-clip" => Some(LineJoin::MiterClip),
"round" => Some(LineJoin::Round),
"bevel" => Some(LineJoin::Bevel),
_ => None,
}
}
}
impl<'a, 'input: 'a> FromValue<'a, 'input> for FillRule {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
match value {
"nonzero" => Some(FillRule::NonZero),
"evenodd" => Some(FillRule::EvenOdd),
_ => None,
}
}
}
pub(crate) fn resolve_fill(
node: SvgNode,
has_bbox: bool,
state: &converter::State,
cache: &mut converter::Cache,
) -> Option<Fill> {
if state.parent_clip_path.is_some() {
return Some(Fill {
paint: Paint::Color(Color::black()),
opacity: Opacity::ONE,
rule: node.find_attribute(AId::ClipRule).unwrap_or_default(),
context_element: None,
});
}
let mut sub_opacity = Opacity::ONE;
let (paint, context_element) =
if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Fill)) {
convert_paint(n, AId::Fill, has_bbox, state, &mut sub_opacity, cache)?
} else {
(Paint::Color(Color::black()), None)
};
let fill_opacity = node
.find_attribute::<Opacity>(AId::FillOpacity)
.unwrap_or(Opacity::ONE);
Some(Fill {
paint,
opacity: sub_opacity * fill_opacity,
rule: node.find_attribute(AId::FillRule).unwrap_or_default(),
context_element,
})
}
pub(crate) fn resolve_stroke(
node: SvgNode,
has_bbox: bool,
state: &converter::State,
cache: &mut converter::Cache,
) -> Option<Stroke> {
if state.parent_clip_path.is_some() {
return None;
}
let mut sub_opacity = Opacity::ONE;
let (paint, context_element) =
if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Stroke)) {
convert_paint(n, AId::Stroke, has_bbox, state, &mut sub_opacity, cache)?
} else {
return None;
};
let width = node.resolve_valid_length(AId::StrokeWidth, state, 1.0)?;
let miterlimit = node.find_attribute(AId::StrokeMiterlimit).unwrap_or(4.0);
let miterlimit = if miterlimit < 1.0 { 1.0 } else { miterlimit };
let miterlimit = StrokeMiterlimit::new(miterlimit);
let stroke_opacity = node
.find_attribute::<Opacity>(AId::StrokeOpacity)
.unwrap_or(Opacity::ONE);
let stroke = Stroke {
paint,
dasharray: conv_dasharray(node, state),
dashoffset: node.resolve_length(AId::StrokeDashoffset, state, 0.0),
miterlimit,
opacity: sub_opacity * stroke_opacity,
width,
linecap: node.find_attribute(AId::StrokeLinecap).unwrap_or_default(),
linejoin: node.find_attribute(AId::StrokeLinejoin).unwrap_or_default(),
context_element,
};
Some(stroke)
}
fn convert_paint(
node: SvgNode,
aid: AId,
has_bbox: bool,
state: &converter::State,
opacity: &mut Opacity,
cache: &mut converter::Cache,
) -> Option<(Paint, Option<ContextElement>)> {
let value: &str = node.attribute(aid)?;
let paint = match svgtypes::Paint::from_str(value) {
Ok(v) => v,
Err(_) => {
if aid == AId::Fill {
log::warn!(
"Failed to parse fill value: '{}'. Fallback to black.",
value
);
svgtypes::Paint::Color(svgtypes::Color::black())
} else if aid == AId::Stroke {
log::warn!(
"Failed to parse stroke value: '{}'. Fallback to no stroke.",
value
);
return None;
} else {
return None;
}
}
};
match paint {
svgtypes::Paint::None => None,
svgtypes::Paint::Inherit => None, svgtypes::Paint::ContextFill => state
.context_element
.clone()
.map(|(f, _)| f)
.flatten()
.map(|f| (f.paint, f.context_element)),
svgtypes::Paint::ContextStroke => state
.context_element
.clone()
.map(|(_, s)| s)
.flatten()
.map(|s| (s.paint, s.context_element)),
svgtypes::Paint::CurrentColor => {
let svg_color: svgtypes::Color = node
.find_attribute(AId::Color)
.unwrap_or_else(svgtypes::Color::black);
let (color, alpha) = svg_color.split_alpha();
*opacity = alpha;
Some((Paint::Color(color), None))
}
svgtypes::Paint::Color(svg_color) => {
let (color, alpha) = svg_color.split_alpha();
*opacity = alpha;
Some((Paint::Color(color), None))
}
svgtypes::Paint::FuncIRI(func_iri, fallback) => {
if let Some(link) = node.document().element_by_id(func_iri) {
let tag_name = link.tag_name().unwrap();
if tag_name.is_paint_server() {
match paint_server::convert(link, state, cache) {
Some(paint_server::ServerOrColor::Server(paint)) => {
if !has_bbox && paint.units() == Units::ObjectBoundingBox {
from_fallback(node, fallback, opacity).map(|p| (p, None))
} else {
Some((paint, None))
}
}
Some(paint_server::ServerOrColor::Color { color, opacity: so }) => {
*opacity = so;
Some((Paint::Color(color), None))
}
None => from_fallback(node, fallback, opacity).map(|p| (p, None)),
}
} else {
log::warn!("'{}' cannot be used to {} a shape.", tag_name, aid);
None
}
} else {
from_fallback(node, fallback, opacity).map(|p| (p, None))
}
}
}
}
fn from_fallback(
node: SvgNode,
fallback: Option<svgtypes::PaintFallback>,
opacity: &mut Opacity,
) -> Option<Paint> {
match fallback? {
svgtypes::PaintFallback::None => None,
svgtypes::PaintFallback::CurrentColor => {
let svg_color: svgtypes::Color = node
.find_attribute(AId::Color)
.unwrap_or_else(svgtypes::Color::black);
let (color, alpha) = svg_color.split_alpha();
*opacity = alpha;
Some(Paint::Color(color))
}
svgtypes::PaintFallback::Color(svg_color) => {
let (color, alpha) = svg_color.split_alpha();
*opacity = alpha;
Some(Paint::Color(color))
}
}
}
fn conv_dasharray(node: SvgNode, state: &converter::State) -> Option<Vec<f32>> {
let node = node
.ancestors()
.find(|n| n.has_attribute(AId::StrokeDasharray))?;
let list = super::units::convert_list(node, AId::StrokeDasharray, state)?;
if list.iter().any(|n| n.is_sign_negative()) {
return None;
}
{
let mut sum: f32 = 0.0;
for n in list.iter() {
sum += *n;
}
if sum.approx_eq_ulps(&0.0, 4) {
return None;
}
}
if list.len() % 2 != 0 {
let mut tmp_list = list.clone();
tmp_list.extend_from_slice(&list);
return Some(tmp_list);
}
Some(list)
}