use std::hash::{Hash, Hasher};
use std::rc::Rc;
use crate::svgtree::{self, AId};
use crate::{
converter, paint_server, FuzzyEq, LinearGradient, Opacity, Pattern, RadialGradient, Units,
};
use strict_num::NonZeroPositiveF64;
macro_rules! wrap {
($name:ident) => {
impl From<f64> for $name {
#[inline]
fn from(n: f64) -> Self {
$name::new(n)
}
}
impl PartialEq for $name {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0.fuzzy_eq(&other.0)
}
}
};
}
pub type StrokeWidth = NonZeroPositiveF64;
#[derive(Clone, Copy, Debug)]
pub struct StrokeMiterlimit(f64);
impl StrokeMiterlimit {
#[inline]
pub fn new(n: f64) -> Self {
debug_assert!(n.is_finite());
debug_assert!(n >= 1.0);
let n = if !(n >= 1.0) { 1.0 } else { n };
StrokeMiterlimit(n)
}
#[inline]
pub fn get(&self) -> f64 {
self.0
}
}
impl Default for StrokeMiterlimit {
#[inline]
fn default() -> Self {
StrokeMiterlimit::new(4.0)
}
}
wrap!(StrokeMiterlimit);
#[allow(missing_docs)]
#[derive(Clone, Copy, Hash, PartialEq, Debug)]
pub enum LineCap {
Butt,
Round,
Square,
}
impl_enum_default!(LineCap, Butt);
impl_enum_from_str!(LineCap,
"butt" => LineCap::Butt,
"round" => LineCap::Round,
"square" => LineCap::Square
);
#[allow(missing_docs)]
#[derive(Clone, Hash, Copy, PartialEq, Debug)]
pub enum LineJoin {
Miter,
Round,
Bevel,
}
impl_enum_default!(LineJoin, Miter);
impl_enum_from_str!(LineJoin,
"miter" => LineJoin::Miter,
"round" => LineJoin::Round,
"bevel" => LineJoin::Bevel
);
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct Stroke {
pub paint: Paint,
pub dasharray: Option<Vec<f64>>,
pub dashoffset: f32, pub miterlimit: StrokeMiterlimit,
pub opacity: Opacity,
pub width: StrokeWidth,
pub linecap: LineCap,
pub linejoin: LineJoin,
}
impl Eq for Stroke {}
impl PartialEq for Stroke {
fn eq(&self, other: &Self) -> bool {
self.paint == other.paint
&& self.dasharray == other.dasharray
&& self.dashoffset == other.dashoffset
&& self.miterlimit == other.miterlimit
&& self.opacity == other.opacity
&& self.width == other.width
&& self.linecap == other.linecap
&& self.linejoin == other.linejoin
}
}
impl Hash for Stroke {
fn hash<H: Hasher>(&self, state: &mut H) {
self.paint.hash(state);
self.dasharray
.as_ref()
.map(|vec| vec.iter().map(|v| v.to_bits()).collect::<Vec<_>>())
.hash(state);
self.miterlimit.0.to_bits().hash(state);
self.opacity.hash(state);
self.width.hash(state);
self.linecap.hash(state);
self.linejoin.hash(state);
}
}
impl Default for Stroke {
fn default() -> Self {
Stroke {
paint: Paint::Color(Color::black()),
dasharray: None,
dashoffset: 0.0,
miterlimit: StrokeMiterlimit::default(),
opacity: Opacity::ONE,
width: StrokeWidth::new(1.0).unwrap(),
linecap: LineCap::default(),
linejoin: LineJoin::default(),
}
}
}
#[allow(missing_docs)]
#[derive(Clone, Hash, Copy, PartialEq, Eq, Debug)]
pub enum FillRule {
NonZero,
EvenOdd,
}
impl_enum_default!(FillRule, NonZero);
impl_enum_from_str!(FillRule,
"nonzero" => FillRule::NonZero,
"evenodd" => FillRule::EvenOdd
);
#[allow(missing_docs)]
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub struct Fill {
pub paint: Paint,
pub opacity: Opacity,
pub rule: FillRule,
}
impl Fill {
pub fn from_paint(paint: Paint) -> Self {
Fill {
paint,
..Fill::default()
}
}
}
impl Default for Fill {
fn default() -> Self {
Fill {
paint: Paint::Color(Color::black()),
opacity: Opacity::ONE,
rule: FillRule::default(),
}
}
}
#[derive(Clone, Hash, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl Color {
#[inline]
pub fn new_rgb(red: u8, green: u8, blue: u8) -> Color {
Color { red, green, blue }
}
#[inline]
pub fn black() -> Color {
Color::new_rgb(0, 0, 0)
}
#[inline]
pub fn white() -> Color {
Color::new_rgb(255, 255, 255)
}
}
pub(crate) trait SvgColorExt {
fn split_alpha(self) -> (Color, Opacity);
}
impl SvgColorExt for svgrtypes::Color {
fn split_alpha(self) -> (Color, Opacity) {
(
Color::new_rgb(self.red, self.green, self.blue),
Opacity::new_u8(self.alpha),
)
}
}
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub enum Paint {
Color(Color),
LinearGradient(Rc<LinearGradient>),
RadialGradient(Rc<RadialGradient>),
Pattern(Rc<Pattern>),
}
impl Paint {
#[inline]
pub fn units(&self) -> Option<Units> {
match self {
Self::Color(_) => None,
Self::LinearGradient(ref lg) => Some(lg.units),
Self::RadialGradient(ref rg) => Some(rg.units),
Self::Pattern(ref patt) => Some(patt.units),
}
}
}
impl PartialEq for Paint {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Color(lc), Self::Color(rc)) => lc == rc,
(Self::LinearGradient(ref lg1), Self::LinearGradient(ref lg2)) => Rc::ptr_eq(lg1, lg2),
(Self::RadialGradient(ref rg1), Self::RadialGradient(ref rg2)) => Rc::ptr_eq(rg1, rg2),
(Self::Pattern(ref p1), Self::Pattern(ref p2)) => Rc::ptr_eq(p1, p2),
_ => false,
}
}
}
impl Eq for Paint {}
impl std::hash::Hash for Paint {
fn hash<H: Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
}
}
pub(crate) fn resolve_fill(
node: svgtree::Node,
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(),
});
}
let mut sub_opacity = Opacity::ONE;
let paint = if let Some(n) = node.find_node_with_attribute(AId::Fill) {
convert_paint(n, AId::Fill, has_bbox, state, &mut sub_opacity, cache)?
} else {
Paint::Color(Color::black())
};
Some(Fill {
paint,
opacity: sub_opacity
* node
.find_attribute(AId::FillOpacity)
.unwrap_or(Opacity::ONE),
rule: node.find_attribute(AId::FillRule).unwrap_or_default(),
})
}
pub(crate) fn resolve_stroke(
node: svgtree::Node,
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 = if let Some(n) = node.find_node_with_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 = Stroke {
paint,
dasharray: conv_dasharray(node, state),
dashoffset: node.resolve_length(AId::StrokeDashoffset, state, 0.0) as f32,
miterlimit,
opacity: sub_opacity
* node
.find_attribute(AId::StrokeOpacity)
.unwrap_or(Opacity::ONE),
width,
linecap: node.find_attribute(AId::StrokeLinecap).unwrap_or_default(),
linejoin: node.find_attribute(AId::StrokeLinejoin).unwrap_or_default(),
};
Some(stroke)
}
fn convert_paint(
node: svgtree::Node,
aid: AId,
has_bbox: bool,
state: &converter::State,
opacity: &mut Opacity,
cache: &mut converter::Cache,
) -> Option<Paint> {
match node.attribute::<&svgtree::AttributeValue>(aid)? {
svgtree::AttributeValue::CurrentColor => {
let svg_color: svgrtypes::Color = node
.find_attribute(AId::Color)
.unwrap_or_else(svgrtypes::Color::black);
let (color, alpha) = svg_color.split_alpha();
*opacity = alpha;
Some(Paint::Color(color))
}
svgtree::AttributeValue::Color(svg_color) => {
let (color, alpha) = svg_color.split_alpha();
*opacity = alpha;
Some(Paint::Color(color))
}
svgtree::AttributeValue::Paint(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() == Some(Units::ObjectBoundingBox) {
from_fallback(node, *fallback, opacity)
} else {
Some(paint)
}
}
Some(paint_server::ServerOrColor::Color { color, opacity: so }) => {
*opacity = so;
Some(Paint::Color(color))
}
None => from_fallback(node, *fallback, opacity),
}
} else {
log::warn!("'{}' cannot be used to {} a shape.", tag_name, aid);
None
}
} else {
from_fallback(node, *fallback, opacity)
}
}
_ => None,
}
}
fn from_fallback(
node: svgtree::Node,
fallback: Option<svgrtypes::PaintFallback>,
opacity: &mut Opacity,
) -> Option<Paint> {
match fallback? {
svgrtypes::PaintFallback::None => None,
svgrtypes::PaintFallback::CurrentColor => {
let svg_color: svgrtypes::Color = node
.find_attribute(AId::Color)
.unwrap_or_else(svgrtypes::Color::black);
let (color, alpha) = svg_color.split_alpha();
*opacity = alpha;
Some(Paint::Color(color))
}
svgrtypes::PaintFallback::Color(svg_color) => {
let (color, alpha) = svg_color.split_alpha();
*opacity = alpha;
Some(Paint::Color(color))
}
}
}
fn conv_dasharray(node: svgtree::Node, state: &converter::State) -> Option<Vec<f64>> {
let node = node.find_node_with_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 = 0.0f64;
for n in list.iter() {
sum += *n;
}
if sum.fuzzy_eq(&0.0) {
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)
}