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