1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 
5 use crate::{svgtree, tree};
6 use super::{prelude::*, paint_server};
7 
8 
resolve_fill( node: svgtree::Node, has_bbox: bool, state: &State, tree: &mut tree::Tree, ) -> Option<tree::Fill>9 pub fn resolve_fill(
10     node: svgtree::Node,
11     has_bbox: bool,
12     state: &State,
13     tree: &mut tree::Tree,
14 ) -> Option<tree::Fill> {
15     if state.parent_clip_path.is_some() {
16         // A `clipPath` child can be filled only with a black color.
17         return Some(tree::Fill {
18             paint: tree::Paint::Color(tree::Color::black()),
19             opacity: tree::Opacity::default(),
20             rule: node.find_attribute(AId::ClipRule).unwrap_or_default(),
21         });
22     }
23 
24     let mut sub_opacity = tree::Opacity::default();
25     let paint = if let Some(n) = node.find_node_with_attribute(AId::Fill) {
26         convert_paint(n, AId::Fill, has_bbox, state, &mut sub_opacity, tree)?
27     } else {
28         tree::Paint::Color(tree::Color::black())
29     };
30 
31     Some(tree::Fill {
32         paint,
33         opacity: sub_opacity * node.find_attribute(AId::FillOpacity).unwrap_or_default(),
34         rule: node.find_attribute(AId::FillRule).unwrap_or_default(),
35     })
36 }
37 
resolve_stroke( node: svgtree::Node, has_bbox: bool, state: &State, tree: &mut tree::Tree, ) -> Option<tree::Stroke>38 pub fn resolve_stroke(
39     node: svgtree::Node,
40     has_bbox: bool,
41     state: &State,
42     tree: &mut tree::Tree,
43 ) -> Option<tree::Stroke> {
44     if state.parent_clip_path.is_some() {
45         // A `clipPath` child cannot be stroked.
46         return None;
47     }
48 
49     let mut sub_opacity = tree::Opacity::default();
50     let paint = if let Some(n) = node.find_node_with_attribute(AId::Stroke) {
51         convert_paint(n, AId::Stroke, has_bbox, state, &mut sub_opacity, tree)?
52     } else {
53         return None;
54     };
55 
56     let width = node.resolve_valid_length(AId::StrokeWidth, state, 1.0)?;
57 
58     // Must be bigger than 1.
59     let miterlimit = node.find_attribute(AId::StrokeMiterlimit).unwrap_or(4.0);
60     let miterlimit = if miterlimit < 1.0 { 1.0 } else { miterlimit };
61     let miterlimit = tree::StrokeMiterlimit::new(miterlimit);
62 
63     let stroke = tree::Stroke {
64         paint,
65         dasharray: conv_dasharray(node, state),
66         dashoffset: node.resolve_length(AId::StrokeDashoffset, state, 0.0) as f32,
67         miterlimit,
68         opacity: sub_opacity * node.find_attribute(AId::StrokeOpacity).unwrap_or_default(),
69         width: tree::StrokeWidth::new(width),
70         linecap: node.find_attribute(AId::StrokeLinecap).unwrap_or_default(),
71         linejoin: node.find_attribute(AId::StrokeLinejoin).unwrap_or_default(),
72     };
73 
74     Some(stroke)
75 }
76 
convert_paint( node: svgtree::Node, aid: AId, has_bbox: bool, state: &State, opacity: &mut tree::Opacity, tree: &mut tree::Tree, ) -> Option<tree::Paint>77 fn convert_paint(
78     node: svgtree::Node,
79     aid: AId,
80     has_bbox: bool,
81     state: &State,
82     opacity: &mut tree::Opacity,
83     tree: &mut tree::Tree,
84 ) -> Option<tree::Paint> {
85     match node.attribute::<&svgtree::AttributeValue>(aid)? {
86         svgtree::AttributeValue::CurrentColor => {
87             let c = node.find_attribute(AId::Color).unwrap_or_else(tree::Color::black);
88             Some(tree::Paint::Color(c))
89         }
90         svgtree::AttributeValue::Color(c) => {
91             Some(tree::Paint::Color(*c))
92         }
93         svgtree::AttributeValue::Paint(func_iri, fallback) => {
94             if let Some(link) = node.document().element_by_id(func_iri) {
95                 let tag_name = link.tag_name().unwrap();
96                 if tag_name.is_paint_server() {
97                     match paint_server::convert(link, state, tree) {
98                         Some(paint_server::ServerOrColor::Server { id, units }) => {
99                             // We can use a paint server node with ObjectBoundingBox units
100                             // for painting only when the shape itself has a bbox.
101                             //
102                             // See SVG spec 7.11 for details.
103                             if !has_bbox && units == tree::Units::ObjectBoundingBox {
104                                 from_fallback(node, *fallback)
105                             } else {
106                                 Some(tree::Paint::Link(id))
107                             }
108                         }
109                         Some(paint_server::ServerOrColor::Color { color, opacity: so }) => {
110                             *opacity = so;
111                             Some(tree::Paint::Color(color))
112                         }
113                         None => {
114                             from_fallback(node, *fallback)
115                         }
116                     }
117                 } else {
118                     warn!("'{}' cannot be used to {} a shape.", tag_name, aid);
119                     None
120                 }
121             } else {
122                 from_fallback(node, *fallback)
123             }
124         }
125         _ => {
126             None
127         }
128     }
129 }
130 
from_fallback( node: svgtree::Node, fallback: Option<svgtypes::PaintFallback>, ) -> Option<tree::Paint>131 fn from_fallback(
132     node: svgtree::Node,
133     fallback: Option<svgtypes::PaintFallback>,
134 ) -> Option<tree::Paint> {
135     match fallback? {
136         svgtypes::PaintFallback::None => {
137             None
138         }
139         svgtypes::PaintFallback::CurrentColor => {
140             let c = node.find_attribute(AId::Color).unwrap_or_else(tree::Color::black);
141             Some(tree::Paint::Color(c))
142         }
143         svgtypes::PaintFallback::Color(c) => {
144             Some(tree::Paint::Color(c))
145         }
146     }
147 }
148 
149 // Prepare the 'stroke-dasharray' according to:
150 // https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty
conv_dasharray( node: svgtree::Node, state: &State, ) -> Option<Vec<f64>>151 fn conv_dasharray(
152     node: svgtree::Node,
153     state: &State,
154 ) -> Option<Vec<f64>> {
155     let node = node.find_node_with_attribute(AId::StrokeDasharray)?;
156     let list = super::units::convert_list(node, AId::StrokeDasharray, state)?;
157 
158     // `A negative value is an error`
159     if list.iter().any(|n| n.is_sign_negative()) {
160         return None;
161     }
162 
163     // `If the sum of the values is zero, then the stroke is rendered
164     // as if a value of none were specified.`
165     {
166         // no Iter::sum(), because of f64
167 
168         let mut sum = 0.0f64;
169         for n in list.iter() {
170             sum += *n;
171         }
172 
173         if sum.fuzzy_eq(&0.0) {
174             return None;
175         }
176     }
177 
178     // `If an odd number of values is provided, then the list of values
179     // is repeated to yield an even number of values.`
180     if list.len() % 2 != 0 {
181         let mut tmp_list = list.clone();
182         tmp_list.extend_from_slice(&list);
183         return Some(tmp_list);
184     }
185 
186     Some(list)
187 }
188