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 std::f64;
6 
7 use crate::{svgtree, tree, tree::prelude::*};
8 use super::prelude::*;
9 
10 
11 pub enum ServerOrColor {
12     Server {
13         id: String,
14         units: tree::Units,
15     },
16     Color {
17         color: tree::Color,
18         opacity: tree::Opacity,
19     },
20 }
21 
convert( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option<ServerOrColor>22 pub fn convert(
23     node: svgtree::Node,
24     state: &State,
25     tree: &mut tree::Tree,
26 ) -> Option<ServerOrColor> {
27     // Check for existing.
28     if let Some(exist_node) = tree.defs_by_id(node.element_id()) {
29         return Some(ServerOrColor::Server {
30             id: node.element_id().to_string(),
31             units: exist_node.units()?,
32         });
33     }
34 
35     // Unwrap is safe, because we already checked for is_paint_server().
36     match node.tag_name().unwrap() {
37         EId::LinearGradient => convert_linear(node, state, tree),
38         EId::RadialGradient => convert_radial(node, state, tree),
39         EId::Pattern => convert_pattern(node, state, tree),
40         _ => unreachable!(),
41     }
42 }
43 
44 #[inline(never)]
convert_linear( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option<ServerOrColor>45 fn convert_linear(
46     node: svgtree::Node,
47     state: &State,
48     tree: &mut tree::Tree,
49 ) -> Option<ServerOrColor> {
50     let stops = convert_stops(find_gradient_with_stops(node)?);
51     if stops.len() < 2 {
52         return stops_to_color(&stops);
53     }
54 
55     let units = convert_units(node, AId::GradientUnits, tree::Units::ObjectBoundingBox);
56     let transform = resolve_attr(node, AId::GradientTransform)
57         .attribute(AId::GradientTransform).unwrap_or_default();
58 
59     tree.append_to_defs(
60         tree::NodeKind::LinearGradient(tree::LinearGradient {
61             id: node.element_id().to_string(),
62             x1: resolve_number(node, AId::X1, units, state, Length::zero()),
63             y1: resolve_number(node, AId::Y1, units, state, Length::zero()),
64             x2: resolve_number(node, AId::X2, units, state, Length::new(100.0, Unit::Percent)),
65             y2: resolve_number(node, AId::Y2, units, state, Length::zero()),
66             base: tree::BaseGradient {
67                 units,
68                 transform,
69                 spread_method: convert_spread_method(node),
70                 stops,
71             }
72         })
73     );
74 
75     Some(ServerOrColor::Server {
76         id: node.element_id().to_string(),
77         units,
78     })
79 }
80 
81 #[inline(never)]
convert_radial( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option<ServerOrColor>82 fn convert_radial(
83     node: svgtree::Node,
84     state: &State,
85     tree: &mut tree::Tree,
86 ) -> Option<ServerOrColor> {
87     let stops = convert_stops(find_gradient_with_stops(node)?);
88     if stops.len() < 2 {
89         return stops_to_color(&stops);
90     }
91 
92     let units = convert_units(node, AId::GradientUnits, tree::Units::ObjectBoundingBox);
93     let r = resolve_number(node, AId::R, units, state, Length::new(50.0, Unit::Percent));
94 
95     // 'A value of zero will cause the area to be painted as a single color
96     // using the color and opacity of the last gradient stop.'
97     //
98     // https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementRAttribute
99     if !r.is_valid_length() {
100         let stop = stops.last().unwrap();
101         return Some(ServerOrColor::Color {
102             color: stop.color,
103             opacity: stop.opacity,
104         });
105     }
106 
107     let spread_method = convert_spread_method(node);
108     let cx = resolve_number(node, AId::Cx, units, state, Length::new(50.0, Unit::Percent));
109     let cy = resolve_number(node, AId::Cy, units, state, Length::new(50.0, Unit::Percent));
110     let fx = resolve_number(node, AId::Fx, units, state, Length::new_number(cx));
111     let fy = resolve_number(node, AId::Fy, units, state, Length::new_number(cy));
112     let (fx, fy) = prepare_focal(cx, cy, r, fx, fy);
113     let transform = resolve_attr(node, AId::GradientTransform)
114         .attribute(AId::GradientTransform).unwrap_or_default();
115 
116     tree.append_to_defs(
117         tree::NodeKind::RadialGradient(tree::RadialGradient {
118             id: node.element_id().to_string(),
119             cx,
120             cy,
121             r: r.into(),
122             fx,
123             fy,
124             base: tree::BaseGradient {
125                 units,
126                 transform,
127                 spread_method,
128                 stops,
129             }
130         })
131     );
132 
133     Some(ServerOrColor::Server {
134         id: node.element_id().to_string(),
135         units,
136     })
137 }
138 
139 #[inline(never)]
convert_pattern( node: svgtree::Node, state: &State, tree: &mut tree::Tree, ) -> Option<ServerOrColor>140 fn convert_pattern(
141     node: svgtree::Node,
142     state: &State,
143     tree: &mut tree::Tree,
144 ) -> Option<ServerOrColor> {
145     let node_with_children = find_pattern_with_children(node)?;
146 
147     let view_box = {
148         let n1 = resolve_attr(node, AId::ViewBox);
149         let n2 = resolve_attr(node, AId::PreserveAspectRatio);
150         n1.get_viewbox().map(|vb|
151             tree::ViewBox {
152                 rect: vb,
153                 aspect: n2.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
154             }
155         )
156     };
157 
158     let units = convert_units(node, AId::PatternUnits, tree::Units::ObjectBoundingBox);
159     let content_units = convert_units(node, AId::PatternContentUnits, tree::Units::UserSpaceOnUse);
160 
161     let transform = resolve_attr(node, AId::PatternTransform)
162         .attribute(AId::PatternTransform).unwrap_or_default();
163 
164     let rect = Rect::new(
165         resolve_number(node, AId::X, units, state, Length::zero()),
166         resolve_number(node, AId::Y, units, state, Length::zero()),
167         resolve_number(node, AId::Width, units, state, Length::zero()),
168         resolve_number(node, AId::Height, units, state, Length::zero()),
169     );
170     let rect = try_opt_warn_or!(
171         rect, None,
172         "Pattern '{}' has an invalid size. Skipped.", node.element_id()
173     );
174 
175     let mut patt = tree.append_to_defs(tree::NodeKind::Pattern(tree::Pattern {
176         id: node.element_id().to_string(),
177         units,
178         content_units,
179         transform,
180         rect,
181         view_box,
182     }));
183 
184     super::convert_children(node_with_children, state, &mut patt, tree);
185 
186     if !patt.has_children() {
187         return None;
188     }
189 
190     Some(ServerOrColor::Server {
191         id: node.element_id().to_string(),
192         units,
193     })
194 }
195 
convert_spread_method(node: svgtree::Node) -> tree::SpreadMethod196 fn convert_spread_method(node: svgtree::Node) -> tree::SpreadMethod {
197     let node = resolve_attr(node, AId::SpreadMethod);
198     node.attribute(AId::SpreadMethod).unwrap_or_default()
199 }
200 
convert_units( node: svgtree::Node, name: AId, def: tree::Units, ) -> tree::Units201 pub fn convert_units(
202     node: svgtree::Node,
203     name: AId,
204     def: tree::Units,
205 ) -> tree::Units {
206     let node = resolve_attr(node, name);
207     node.attribute(name).unwrap_or(def)
208 }
209 
find_gradient_with_stops(node: svgtree::Node) -> Option<svgtree::Node>210 fn find_gradient_with_stops(node: svgtree::Node) -> Option<svgtree::Node> {
211     for link_id in node.href_iter() {
212         let link = node.document().get(link_id);
213         if !link.tag_name().unwrap().is_gradient() {
214             warn!(
215                 "Gradient '{}' cannot reference '{}' via 'xlink:href'.",
216                 node.element_id(), link.tag_name().unwrap()
217             );
218             return None;
219         }
220 
221         if link.children().any(|n| n.has_tag_name(EId::Stop)) {
222             return Some(link.clone());
223         }
224     }
225 
226     None
227 }
228 
find_pattern_with_children(node: svgtree::Node) -> Option<svgtree::Node>229 fn find_pattern_with_children(node: svgtree::Node) -> Option<svgtree::Node> {
230     for link_id in node.href_iter() {
231         let link = node.document().get(link_id);
232         if !link.has_tag_name(EId::Pattern) {
233             warn!(
234                 "Pattern '{}' cannot reference '{}' via 'xlink:href'.",
235                 node.element_id(), link.tag_name().unwrap()
236             );
237             return None;
238         }
239 
240         if link.has_children() {
241             return Some(link.clone());
242         }
243     }
244 
245     None
246 }
247 
convert_stops(grad: svgtree::Node) -> Vec<tree::Stop>248 fn convert_stops(grad: svgtree::Node) -> Vec<tree::Stop> {
249     let mut stops = Vec::new();
250 
251     {
252         let mut prev_offset = Length::zero();
253         for stop in grad.children() {
254             if !stop.has_tag_name(EId::Stop) {
255                 warn!("Invalid gradient child: '{:?}'.", stop.tag_name().unwrap());
256                 continue;
257             }
258 
259             // `number` can be either a number or a percentage.
260             let offset = stop.attribute(AId::Offset).unwrap_or(prev_offset);
261             let offset = match offset.unit {
262                 Unit::None => offset.num,
263                 Unit::Percent => offset.num / 100.0,
264                 _ => prev_offset.num,
265             };
266             let offset = crate::utils::f64_bound(0.0, offset, 1.0);
267             prev_offset = Length::new_number(offset);
268 
269             let color = match stop.attribute(AId::StopColor) {
270                 Some(&svgtree::AttributeValue::CurrentColor) => {
271                     stop.find_attribute(AId::Color).unwrap_or_else(tree::Color::black)
272                 }
273                 Some(&svgtree::AttributeValue::Color(c)) => {
274                     c
275                 }
276                 _ => {
277                     svgtypes::Color::black()
278                 }
279             };
280 
281             stops.push(tree::Stop {
282                 offset: offset.into(),
283                 color,
284                 opacity: stop.attribute(AId::StopOpacity).unwrap_or_default(),
285             });
286         }
287     }
288 
289     // Remove stops with equal offset.
290     //
291     // Example:
292     // offset="0.5"
293     // offset="0.7"
294     // offset="0.7" <-- this one should be removed
295     // offset="0.7"
296     // offset="0.9"
297     if stops.len() >= 3 {
298         let mut i = 0;
299         while i < stops.len() - 2 {
300             let offset1 = stops[i + 0].offset.value();
301             let offset2 = stops[i + 1].offset.value();
302             let offset3 = stops[i + 2].offset.value();
303 
304             if offset1.fuzzy_eq(&offset2) && offset2.fuzzy_eq(&offset3) {
305                 // Remove offset in the middle.
306                 stops.remove(i + 1);
307             } else {
308                 i += 1;
309             }
310         }
311     }
312 
313     // Remove zeros.
314     //
315     // From:
316     // offset="0.0"
317     // offset="0.0"
318     // offset="0.7"
319     //
320     // To:
321     // offset="0.0"
322     // offset="0.00000001"
323     // offset="0.7"
324     if stops.len() >= 2 {
325         let mut i = 0;
326         while i < stops.len() - 1 {
327             let offset1 = stops[i + 0].offset.value();
328             let offset2 = stops[i + 1].offset.value();
329 
330             if offset1.is_fuzzy_zero() && offset2.is_fuzzy_zero() {
331                 stops[i + 1].offset = (offset1 + f64::EPSILON).into();
332             }
333 
334             i += 1;
335         }
336     }
337 
338     // Shift equal offsets.
339     //
340     // From:
341     // offset="0.5"
342     // offset="0.7"
343     // offset="0.7"
344     //
345     // To:
346     // offset="0.5"
347     // offset="0.699999999"
348     // offset="0.7"
349     {
350         let mut i = 1;
351         while i < stops.len() {
352             let offset1 = stops[i - 1].offset.value();
353             let offset2 = stops[i - 0].offset.value();
354 
355             // Next offset must be smaller then previous.
356             if offset1 > offset2 || offset1.fuzzy_eq(&offset2) {
357                 // Make previous offset a bit smaller.
358                 let new_offset = offset1 - f64::EPSILON;
359                 stops[i - 1].offset = crate::utils::f64_bound(0.0, new_offset, 1.0).into();
360                 stops[i - 0].offset = offset1.into();
361             }
362 
363             i += 1;
364         }
365     }
366 
367     stops
368 }
369 
370 #[inline(never)]
resolve_number( node: svgtree::Node, name: AId, units: tree::Units, state: &State, def: Length ) -> f64371 pub fn resolve_number(
372     node: svgtree::Node, name: AId, units: tree::Units, state: &State, def: Length
373 ) -> f64 {
374     resolve_attr(node, name).convert_length(name, units, state, def)
375 }
376 
resolve_attr( node: svgtree::Node, name: AId, ) -> svgtree::Node377 fn resolve_attr(
378     node: svgtree::Node,
379     name: AId,
380 ) -> svgtree::Node {
381     if node.has_attribute(name) {
382         return node;
383     }
384 
385     match node.tag_name().unwrap() {
386         EId::LinearGradient => resolve_lg_attr(node, name),
387         EId::RadialGradient => resolve_rg_attr(node, name),
388         EId::Pattern => resolve_pattern_attr(node, name),
389         EId::Filter => resolve_filter_attr(node, name),
390         _ => node,
391     }
392 }
393 
resolve_lg_attr( node: svgtree::Node, name: AId, ) -> svgtree::Node394 fn resolve_lg_attr(
395     node: svgtree::Node,
396     name: AId,
397 ) -> svgtree::Node {
398     for link_id in node.href_iter() {
399         let link = node.document().get(link_id);
400         let tag_name = try_opt_or!(link.tag_name(), node);
401         match (name, tag_name) {
402             // Coordinates can be resolved only from
403             // ref element with the same type.
404               (AId::X1, EId::LinearGradient)
405             | (AId::Y1, EId::LinearGradient)
406             | (AId::X2, EId::LinearGradient)
407             | (AId::Y2, EId::LinearGradient)
408             // Other attributes can be resolved
409             // from any kind of gradient.
410             | (AId::GradientUnits, EId::LinearGradient)
411             | (AId::GradientUnits, EId::RadialGradient)
412             | (AId::SpreadMethod, EId::LinearGradient)
413             | (AId::SpreadMethod, EId::RadialGradient)
414             | (AId::GradientTransform, EId::LinearGradient)
415             | (AId::GradientTransform, EId::RadialGradient) => {
416                 if link.has_attribute(name) {
417                     return link;
418                 }
419             }
420             _ => break,
421         }
422     }
423 
424     node
425 }
426 
resolve_rg_attr( node: svgtree::Node, name: AId, ) -> svgtree::Node427 fn resolve_rg_attr(
428     node: svgtree::Node,
429     name: AId,
430 ) -> svgtree::Node {
431     for link_id in node.href_iter() {
432         let link = node.document().get(link_id);
433         let tag_name = try_opt_or!(link.tag_name(), node);
434         match (name, tag_name) {
435             // Coordinates can be resolved only from
436             // ref element with the same type.
437               (AId::Cx, EId::RadialGradient)
438             | (AId::Cy, EId::RadialGradient)
439             | (AId::R,  EId::RadialGradient)
440             | (AId::Fx, EId::RadialGradient)
441             | (AId::Fy, EId::RadialGradient)
442             // Other attributes can be resolved
443             // from any kind of gradient.
444             | (AId::GradientUnits, EId::LinearGradient)
445             | (AId::GradientUnits, EId::RadialGradient)
446             | (AId::SpreadMethod, EId::LinearGradient)
447             | (AId::SpreadMethod, EId::RadialGradient)
448             | (AId::GradientTransform, EId::LinearGradient)
449             | (AId::GradientTransform, EId::RadialGradient) => {
450                 if link.has_attribute(name) {
451                     return link;
452                 }
453             }
454             _ => break,
455         }
456     }
457 
458     node
459 }
460 
resolve_pattern_attr( node: svgtree::Node, name: AId, ) -> svgtree::Node461 fn resolve_pattern_attr(
462     node: svgtree::Node,
463     name: AId,
464 ) -> svgtree::Node {
465     for link_id in node.href_iter() {
466         let link = node.document().get(link_id);
467         let tag_name = try_opt_or!(link.tag_name(), node);
468 
469         if tag_name != EId::Pattern {
470             break;
471         }
472 
473         if link.has_attribute(name) {
474             return link;
475         }
476     }
477 
478     node
479 }
480 
resolve_filter_attr( node: svgtree::Node, aid: AId, ) -> svgtree::Node481 fn resolve_filter_attr(
482     node: svgtree::Node,
483     aid: AId,
484 ) -> svgtree::Node {
485     for link_id in node.href_iter() {
486         let link = node.document().get(link_id);
487         let tag_name = try_opt_or!(link.tag_name(), node);
488 
489         if tag_name != EId::Filter {
490             break;
491         }
492 
493         if link.has_attribute(aid) {
494             return link;
495         }
496     }
497 
498     node
499 }
500 
501 /// Prepares the radial gradient focal radius.
502 ///
503 /// According to the SVG spec:
504 ///
505 /// If the point defined by `fx` and `fy` lies outside the circle defined by
506 /// `cx`, `cy` and `r`, then the user agent shall set the focal point to the
507 /// intersection of the line from (`cx`, `cy`) to (`fx`, `fy`) with the circle
508 /// defined by `cx`, `cy` and `r`.
prepare_focal(cx: f64, cy: f64, r: f64, fx: f64, fy: f64) -> (f64, f64)509 fn prepare_focal(cx: f64, cy: f64, r: f64, fx: f64, fy: f64) -> (f64, f64) {
510     let max_r = r - r * 0.001;
511 
512     let mut line = Line::new(cx, cy, fx, fy);
513 
514     if line.length() > max_r {
515         line.set_length(max_r);
516     }
517 
518     (line.x2, line.y2)
519 }
520 
stops_to_color( stops: &[tree::Stop], ) -> Option<ServerOrColor>521 fn stops_to_color(
522     stops: &[tree::Stop],
523 ) -> Option<ServerOrColor> {
524     if stops.is_empty() {
525         None
526     } else {
527         Some(ServerOrColor::Color {
528             color: stops[0].color,
529             opacity: stops[0].opacity,
530         })
531     }
532 }
533