usvg/parser/
clippath.rs
1use std::str::FromStr;
6use std::sync::Arc;
7
8use super::converter;
9use super::svgtree::{AId, EId, SvgNode};
10use crate::{ClipPath, Group, NonEmptyString, NonZeroRect, Transform, Units};
11
12pub(crate) fn convert(
13 node: SvgNode,
14 state: &converter::State,
15 object_bbox: Option<NonZeroRect>,
16 cache: &mut converter::Cache,
17) -> Option<Arc<ClipPath>> {
18 if node.tag_name() != Some(EId::ClipPath) {
20 return None;
21 }
22
23 let mut transform = resolve_clip_path_transform(node, state)?;
25
26 let units = node
27 .attribute(AId::ClipPathUnits)
28 .unwrap_or(Units::UserSpaceOnUse);
29
30 let cacheable = units == Units::UserSpaceOnUse;
36 if cacheable {
37 if let Some(clip) = cache.clip_paths.get(node.element_id()) {
38 return Some(clip.clone());
39 }
40 }
41
42 if units == Units::ObjectBoundingBox {
43 let object_bbox = match object_bbox {
44 Some(v) => v,
45 None => {
46 log::warn!("Clipping of zero-sized shapes is not allowed.");
47 return None;
48 }
49 };
50
51 let ts = Transform::from_bbox(object_bbox);
52 transform = transform.pre_concat(ts);
53 }
54
55 let mut clip_path = None;
57 if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {
58 clip_path = convert(link, state, object_bbox, cache);
59
60 if clip_path.is_none() {
62 return None;
63 }
64 }
65
66 let mut id = NonEmptyString::new(node.element_id().to_string())?;
67 if !cacheable && cache.clip_paths.contains_key(id.get()) {
69 id = cache.gen_clip_path_id();
70 }
71 let id_copy = id.get().to_string();
72
73 let mut clip = ClipPath {
74 id,
75 transform,
76 clip_path,
77 root: Group::empty(),
78 };
79
80 let mut clip_state = state.clone();
81 clip_state.parent_clip_path = Some(node);
82 converter::convert_clip_path_elements(node, &clip_state, cache, &mut clip.root);
83
84 if clip.root.has_children() {
85 clip.root.calculate_bounding_boxes();
86 let clip = Arc::new(clip);
87 cache.clip_paths.insert(id_copy, clip.clone());
88 Some(clip)
89 } else {
90 None
92 }
93}
94
95fn resolve_clip_path_transform(node: SvgNode, state: &converter::State) -> Option<Transform> {
96 let value: &str = match node.attribute(AId::Transform) {
100 Some(v) => v,
101 None => return Some(Transform::default()),
102 };
103
104 let ts = match svgtypes::Transform::from_str(value) {
105 Ok(v) => v,
106 Err(_) => {
107 log::warn!("Failed to parse {} value: '{}'.", AId::Transform, value);
108 return None;
109 }
110 };
111
112 let ts = Transform::from_row(
113 ts.a as f32,
114 ts.b as f32,
115 ts.c as f32,
116 ts.d as f32,
117 ts.e as f32,
118 ts.f as f32,
119 );
120
121 if ts.is_valid() {
122 Some(node.resolve_transform(AId::Transform, state))
123 } else {
124 None
125 }
126}