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