1use super::converter::{self, SvgColorExt};
5use super::paint_server;
6use super::svgtree::{AId, FromValue, SvgNode};
7use crate::tree::ContextElement;
8use crate::{
9 ApproxEqUlps, Color, Fill, FillRule, LineCap, LineJoin, Opacity, Paint, Stroke,
10 StrokeMiterlimit, Units,
11};
12
13impl<'a, 'input: 'a> FromValue<'a, 'input> for LineCap {
14 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
15 match value {
16 "butt" => Some(LineCap::Butt),
17 "round" => Some(LineCap::Round),
18 "square" => Some(LineCap::Square),
19 _ => None,
20 }
21 }
22}
23
24impl<'a, 'input: 'a> FromValue<'a, 'input> for LineJoin {
25 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
26 match value {
27 "miter" => Some(LineJoin::Miter),
28 "miter-clip" => Some(LineJoin::MiterClip),
29 "round" => Some(LineJoin::Round),
30 "bevel" => Some(LineJoin::Bevel),
31 _ => None,
32 }
33 }
34}
35
36impl<'a, 'input: 'a> FromValue<'a, 'input> for FillRule {
37 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
38 match value {
39 "nonzero" => Some(FillRule::NonZero),
40 "evenodd" => Some(FillRule::EvenOdd),
41 _ => None,
42 }
43 }
44}
45
46pub(crate) fn resolve_fill(
47 node: SvgNode,
48 has_bbox: bool,
49 state: &converter::State,
50 cache: &mut converter::Cache,
51) -> Option<Fill> {
52 if state.parent_clip_path.is_some() {
53 return Some(Fill {
55 paint: Paint::Color(Color::black()),
56 opacity: Opacity::ONE,
57 rule: node.find_attribute(AId::ClipRule).unwrap_or_default(),
58 context_element: None,
59 });
60 }
61
62 let mut sub_opacity = Opacity::ONE;
63 let (paint, context_element) =
64 if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Fill)) {
65 convert_paint(n, AId::Fill, has_bbox, state, &mut sub_opacity, cache)?
66 } else {
67 (Paint::Color(Color::black()), None)
68 };
69
70 let fill_opacity = node
71 .find_attribute::<Opacity>(AId::FillOpacity)
72 .unwrap_or(Opacity::ONE);
73
74 Some(Fill {
75 paint,
76 opacity: sub_opacity * fill_opacity,
77 rule: node.find_attribute(AId::FillRule).unwrap_or_default(),
78 context_element,
79 })
80}
81
82pub(crate) fn resolve_stroke(
83 node: SvgNode,
84 has_bbox: bool,
85 state: &converter::State,
86 cache: &mut converter::Cache,
87) -> Option<Stroke> {
88 if state.parent_clip_path.is_some() {
89 return None;
91 }
92
93 let mut sub_opacity = Opacity::ONE;
94 let (paint, context_element) =
95 if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Stroke)) {
96 convert_paint(n, AId::Stroke, has_bbox, state, &mut sub_opacity, cache)?
97 } else {
98 return None;
99 };
100
101 let width = node.resolve_valid_length(AId::StrokeWidth, state, 1.0)?;
102
103 let miterlimit = node.find_attribute(AId::StrokeMiterlimit).unwrap_or(4.0);
105 let miterlimit = if miterlimit < 1.0 { 1.0 } else { miterlimit };
106 let miterlimit = StrokeMiterlimit::new(miterlimit);
107
108 let stroke_opacity = node
109 .find_attribute::<Opacity>(AId::StrokeOpacity)
110 .unwrap_or(Opacity::ONE);
111
112 let stroke = Stroke {
113 paint,
114 dasharray: conv_dasharray(node, state),
115 dashoffset: node.resolve_length(AId::StrokeDashoffset, state, 0.0),
116 miterlimit,
117 opacity: sub_opacity * stroke_opacity,
118 width,
119 linecap: node.find_attribute(AId::StrokeLinecap).unwrap_or_default(),
120 linejoin: node.find_attribute(AId::StrokeLinejoin).unwrap_or_default(),
121 context_element,
122 };
123
124 Some(stroke)
125}
126
127fn convert_paint(
128 node: SvgNode,
129 aid: AId,
130 has_bbox: bool,
131 state: &converter::State,
132 opacity: &mut Opacity,
133 cache: &mut converter::Cache,
134) -> Option<(Paint, Option<ContextElement>)> {
135 let value: &str = node.attribute(aid)?;
136 let paint = match svgtypes::Paint::from_str(value) {
137 Ok(v) => v,
138 Err(_) => {
139 if aid == AId::Fill {
140 log::warn!(
141 "Failed to parse fill value: '{}'. Fallback to black.",
142 value
143 );
144 svgtypes::Paint::Color(svgtypes::Color::black())
145 } else if aid == AId::Stroke {
146 log::warn!(
147 "Failed to parse stroke value: '{}'. Fallback to no stroke.",
148 value
149 );
150 return None;
151 } else {
152 return None;
153 }
154 }
155 };
156
157 match paint {
158 svgtypes::Paint::None => None,
159 svgtypes::Paint::Inherit => None, svgtypes::Paint::ContextFill => state
161 .context_element
162 .clone()
163 .and_then(|(f, _)| f)
164 .map(|f| (f.paint, f.context_element)),
165 svgtypes::Paint::ContextStroke => state
166 .context_element
167 .clone()
168 .and_then(|(_, s)| s)
169 .map(|s| (s.paint, s.context_element)),
170 svgtypes::Paint::CurrentColor => {
171 let svg_color: svgtypes::Color = node
172 .find_attribute(AId::Color)
173 .unwrap_or_else(svgtypes::Color::black);
174 let (color, alpha) = svg_color.split_alpha();
175 *opacity = alpha;
176 Some((Paint::Color(color), None))
177 }
178 svgtypes::Paint::Color(svg_color) => {
179 let (color, alpha) = svg_color.split_alpha();
180 *opacity = alpha;
181 Some((Paint::Color(color), None))
182 }
183 svgtypes::Paint::FuncIRI(func_iri, fallback) => {
184 if let Some(link) = node.document().element_by_id(func_iri) {
185 let tag_name = link.tag_name().unwrap();
186 if tag_name.is_paint_server() {
187 match paint_server::convert(link, state, cache) {
188 Some(paint_server::ServerOrColor::Server(paint)) => {
189 if !has_bbox && paint.units() == Units::ObjectBoundingBox {
195 from_fallback(node, fallback, opacity).map(|p| (p, None))
196 } else {
197 Some((paint, None))
198 }
199 }
200 Some(paint_server::ServerOrColor::Color { color, opacity: so }) => {
201 *opacity = so;
202 Some((Paint::Color(color), None))
203 }
204 None => from_fallback(node, fallback, opacity).map(|p| (p, None)),
205 }
206 } else {
207 log::warn!("'{}' cannot be used to {} a shape.", tag_name, aid);
208 None
209 }
210 } else {
211 from_fallback(node, fallback, opacity).map(|p| (p, None))
212 }
213 }
214 }
215}
216
217fn from_fallback(
218 node: SvgNode,
219 fallback: Option<svgtypes::PaintFallback>,
220 opacity: &mut Opacity,
221) -> Option<Paint> {
222 match fallback? {
223 svgtypes::PaintFallback::None => None,
224 svgtypes::PaintFallback::CurrentColor => {
225 let svg_color: svgtypes::Color = node
226 .find_attribute(AId::Color)
227 .unwrap_or_else(svgtypes::Color::black);
228 let (color, alpha) = svg_color.split_alpha();
229 *opacity = alpha;
230 Some(Paint::Color(color))
231 }
232 svgtypes::PaintFallback::Color(svg_color) => {
233 let (color, alpha) = svg_color.split_alpha();
234 *opacity = alpha;
235 Some(Paint::Color(color))
236 }
237 }
238}
239
240fn conv_dasharray(node: SvgNode, state: &converter::State) -> Option<Vec<f32>> {
243 let node = node
244 .ancestors()
245 .find(|n| n.has_attribute(AId::StrokeDasharray))?;
246 let list = super::units::convert_list(node, AId::StrokeDasharray, state)?;
247
248 if list.iter().any(|n| n.is_sign_negative()) {
250 return None;
251 }
252
253 {
256 let mut sum: f32 = 0.0;
259 for n in list.iter() {
260 sum += *n;
261 }
262
263 if sum.approx_eq_ulps(&0.0, 4) {
264 return None;
265 }
266 }
267
268 if list.len() % 2 != 0 {
271 let mut tmp_list = list.clone();
272 tmp_list.extend_from_slice(&list);
273 return Some(tmp_list);
274 }
275
276 Some(list)
277}