1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use std::str::FromStr;
5use std::sync::Arc;
6
7use super::converter;
8use super::svgtree::{AId, EId, SvgNode};
9use crate::{ClipPath, Group, NonEmptyString, NonZeroRect, Transform, Units};
10
11pub(crate) fn convert(
12 node: SvgNode,
13 state: &converter::State,
14 object_bbox: Option<NonZeroRect>,
15 cache: &mut converter::Cache,
16) -> Option<Arc<ClipPath>> {
17 // A `clip-path` attribute must reference a `clipPath` element.
18 if node.tag_name() != Some(EId::ClipPath) {
19 return None;
20 }
21
22 // The whole clip path should be ignored when a transform is invalid.
23 let mut transform = resolve_clip_path_transform(node, state)?;
24
25 let units = node
26 .attribute(AId::ClipPathUnits)
27 .unwrap_or(Units::UserSpaceOnUse);
28
29 // Check if this element was already converted.
30 //
31 // Only `userSpaceOnUse` clipPaths can be shared,
32 // because `objectBoundingBox` one will be converted into user one
33 // and will become node-specific.
34 let cacheable = units == Units::UserSpaceOnUse;
35 if cacheable {
36 if let Some(clip) = cache.clip_paths.get(node.element_id()) {
37 return Some(clip.clone());
38 }
39 }
40
41 if units == Units::ObjectBoundingBox {
42 let object_bbox = match object_bbox {
43 Some(v) => v,
44 None => {
45 log::warn!("Clipping of zero-sized shapes is not allowed.");
46 return None;
47 }
48 };
49
50 let ts = Transform::from_bbox(object_bbox);
51 transform = transform.pre_concat(ts);
52 }
53
54 // Resolve linked clip path.
55 let mut clip_path = None;
56 if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {
57 clip_path = convert(link, state, object_bbox, cache);
58
59 // Linked `clipPath` must be valid.
60 if clip_path.is_none() {
61 return None;
62 }
63 }
64
65 let mut id = NonEmptyString::new(node.element_id().to_string())?;
66 // Generate ID only when we're parsing `objectBoundingBox` clip for the second time.
67 if !cacheable && cache.clip_paths.contains_key(id.get()) {
68 id = cache.gen_clip_path_id();
69 }
70 let id_copy = id.get().to_string();
71
72 let mut clip = ClipPath {
73 id,
74 transform,
75 clip_path,
76 root: Group::empty(),
77 };
78
79 let mut clip_state = state.clone();
80 clip_state.parent_clip_path = Some(node);
81 converter::convert_clip_path_elements(node, &clip_state, cache, &mut clip.root);
82
83 if clip.root.has_children() {
84 clip.root.calculate_bounding_boxes();
85 let clip = Arc::new(clip);
86 cache.clip_paths.insert(id_copy, clip.clone());
87 Some(clip)
88 } else {
89 // A clip path without children is invalid.
90 None
91 }
92}
93
94fn resolve_clip_path_transform(node: SvgNode, state: &converter::State) -> Option<Transform> {
95 // Do not use Node::attribute::<Transform>, because it will always
96 // return a valid transform.
97
98 let value: &str = match node.attribute(AId::Transform) {
99 Some(v) => v,
100 None => return Some(Transform::default()),
101 };
102
103 let ts = match svgtypes::Transform::from_str(value) {
104 Ok(v) => v,
105 Err(_) => {
106 log::warn!("Failed to parse {} value: '{}'.", AId::Transform, value);
107 return None;
108 }
109 };
110
111 let ts = Transform::from_row(
112 ts.a as f32,
113 ts.b as f32,
114 ts.c as f32,
115 ts.d as f32,
116 ts.e as f32,
117 ts.f as f32,
118 );
119
120 if ts.is_valid() {
121 Some(node.resolve_transform(AId::Transform, state))
122 } else {
123 None
124 }
125}
126