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