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 let value: &str = n.attribute(AId::Fill)?;
66 convert_paint(
67 node,
68 value,
69 AId::Fill,
70 has_bbox,
71 state,
72 &mut sub_opacity,
73 cache,
74 )?
75 } else {
76 (Paint::Color(Color::black()), None)
77 };
78
79 let fill_opacity = node
80 .find_attribute::<Opacity>(AId::FillOpacity)
81 .unwrap_or(Opacity::ONE);
82
83 Some(Fill {
84 paint,
85 opacity: sub_opacity * fill_opacity,
86 rule: node.find_attribute(AId::FillRule).unwrap_or_default(),
87 context_element,
88 })
89}
90
91pub(crate) fn resolve_stroke(
92 node: SvgNode,
93 has_bbox: bool,
94 state: &converter::State,
95 cache: &mut converter::Cache,
96) -> Option<Stroke> {
97 if state.parent_clip_path.is_some() {
98 return None;
100 }
101
102 let mut sub_opacity = Opacity::ONE;
103 let (paint, context_element) =
104 if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::Stroke)) {
105 let value: &str = n.attribute(AId::Stroke)?;
106
107 convert_paint(
108 node,
109 value,
110 AId::Stroke,
111 has_bbox,
112 state,
113 &mut sub_opacity,
114 cache,
115 )?
116 } else {
117 return None;
118 };
119
120 let width = node.resolve_valid_length(AId::StrokeWidth, state, 1.0)?;
121
122 let miterlimit = node.find_attribute(AId::StrokeMiterlimit).unwrap_or(4.0);
124 let miterlimit = if miterlimit < 1.0 { 1.0 } else { miterlimit };
125 let miterlimit = StrokeMiterlimit::new(miterlimit);
126
127 let stroke_opacity = node
128 .find_attribute::<Opacity>(AId::StrokeOpacity)
129 .unwrap_or(Opacity::ONE);
130
131 let stroke = Stroke {
132 paint,
133 dasharray: conv_dasharray(node, state),
134 dashoffset: node.resolve_length(AId::StrokeDashoffset, state, 0.0),
135 miterlimit,
136 opacity: sub_opacity * stroke_opacity,
137 width,
138 linecap: node.find_attribute(AId::StrokeLinecap).unwrap_or_default(),
139 linejoin: node.find_attribute(AId::StrokeLinejoin).unwrap_or_default(),
140 context_element,
141 };
142
143 Some(stroke)
144}
145
146fn convert_paint(
147 node: SvgNode,
148 value: &str,
149 aid: AId,
150 has_bbox: bool,
151 state: &converter::State,
152 opacity: &mut Opacity,
153 cache: &mut converter::Cache,
154) -> Option<(Paint, Option<ContextElement>)> {
155 let paint = match svgtypes::Paint::from_str(value) {
156 Ok(v) => v,
157 Err(_) => {
158 if aid == AId::Fill {
159 log::warn!(
160 "Failed to parse fill value: '{}'. Fallback to black.",
161 value
162 );
163 svgtypes::Paint::Color(svgtypes::Color::black())
164 } else if aid == AId::Stroke {
165 log::warn!(
166 "Failed to parse stroke value: '{}'. Fallback to no stroke.",
167 value
168 );
169 return None;
170 } else {
171 return None;
172 }
173 }
174 };
175
176 match paint {
177 svgtypes::Paint::None => None,
178 svgtypes::Paint::Inherit => None, svgtypes::Paint::ContextFill => state
180 .context_element
181 .clone()
182 .and_then(|(f, _)| f)
183 .map(|f| (f.paint, f.context_element)),
184 svgtypes::Paint::ContextStroke => state
185 .context_element
186 .clone()
187 .and_then(|(_, s)| s)
188 .map(|s| (s.paint, s.context_element)),
189 svgtypes::Paint::CurrentColor => {
190 let svg_color: svgtypes::Color = node
191 .find_attribute(AId::Color)
192 .unwrap_or_else(svgtypes::Color::black);
193 let (color, alpha) = svg_color.split_alpha();
194 *opacity = alpha;
195 Some((Paint::Color(color), None))
196 }
197 svgtypes::Paint::Color(svg_color) => {
198 let (color, alpha) = svg_color.split_alpha();
199 *opacity = alpha;
200 Some((Paint::Color(color), None))
201 }
202 svgtypes::Paint::FuncIRI(func_iri, fallback) => {
203 if let Some(link) = node.document().element_by_id(func_iri) {
204 let tag_name = link.tag_name().unwrap();
205 if tag_name.is_paint_server() {
206 match paint_server::convert(link, state, cache) {
207 Some(paint_server::ServerOrColor::Server(paint)) => {
208 if !has_bbox && paint.units() == Units::ObjectBoundingBox {
214 from_fallback(node, fallback, opacity).map(|p| (p, None))
215 } else {
216 Some((paint, None))
217 }
218 }
219 Some(paint_server::ServerOrColor::Color { color, opacity: so }) => {
220 *opacity = so;
221 Some((Paint::Color(color), None))
222 }
223 None => from_fallback(node, fallback, opacity).map(|p| (p, None)),
224 }
225 } else {
226 log::warn!("'{}' cannot be used to {} a shape.", tag_name, aid);
227 None
228 }
229 } else {
230 from_fallback(node, fallback, opacity).map(|p| (p, None))
231 }
232 }
233 }
234}
235
236fn from_fallback(
237 node: SvgNode,
238 fallback: Option<svgtypes::PaintFallback>,
239 opacity: &mut Opacity,
240) -> Option<Paint> {
241 match fallback? {
242 svgtypes::PaintFallback::None => None,
243 svgtypes::PaintFallback::CurrentColor => {
244 let svg_color: svgtypes::Color = node
245 .find_attribute(AId::Color)
246 .unwrap_or_else(svgtypes::Color::black);
247 let (color, alpha) = svg_color.split_alpha();
248 *opacity = alpha;
249 Some(Paint::Color(color))
250 }
251 svgtypes::PaintFallback::Color(svg_color) => {
252 let (color, alpha) = svg_color.split_alpha();
253 *opacity = alpha;
254 Some(Paint::Color(color))
255 }
256 }
257}
258
259fn conv_dasharray(node: SvgNode, state: &converter::State) -> Option<Vec<f32>> {
262 let node = node
263 .ancestors()
264 .find(|n| n.has_attribute(AId::StrokeDasharray))?;
265 let list = super::units::convert_list(node, AId::StrokeDasharray, state)?;
266
267 if list.iter().any(|n| n.is_sign_negative()) {
269 return None;
270 }
271
272 {
275 let mut sum: f32 = 0.0;
278 for n in list.iter() {
279 sum += *n;
280 }
281
282 if sum.approx_eq_ulps(&0.0, 4) {
283 return None;
284 }
285 }
286
287 if list.len() % 2 != 0 {
290 let mut tmp_list = list.clone();
291 tmp_list.extend_from_slice(&list);
292 return Some(tmp_list);
293 }
294
295 Some(list)
296}