usvg/parser/
use_node.rs

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
5use std::sync::Arc;
6
7use svgtypes::{Length, LengthUnit};
8
9use super::svgtree::{AId, EId, SvgNode};
10use super::{converter, style};
11use crate::tree::ContextElement;
12use crate::{Group, IsValidLength, Node, NonZeroRect, Path, Size, Transform, ViewBox};
13
14pub(crate) fn convert(
15    node: SvgNode,
16    state: &converter::State,
17    cache: &mut converter::Cache,
18    parent: &mut Group,
19) {
20    let child = match node.first_child() {
21        Some(v) => v,
22        None => return,
23    };
24
25    if state.parent_clip_path.is_some() && child.tag_name() == Some(EId::Symbol) {
26        // Ignore `symbol` referenced by `use` inside a `clipPath`.
27        // It will be ignored later anyway, but this will prevent
28        // a redundant `clipPath` creation (which is required for `symbol`).
29        return;
30    }
31
32    let mut use_state = state.clone();
33    use_state.context_element = Some((
34        style::resolve_fill(node, true, state, cache).map(|mut f| {
35            f.context_element = Some(ContextElement::UseNode);
36            f
37        }),
38        style::resolve_stroke(node, true, state, cache).map(|mut s| {
39            s.context_element = Some(ContextElement::UseNode);
40            s
41        }),
42    ));
43
44    // We require an original transformation to setup 'clipPath'.
45    let mut orig_ts = node.resolve_transform(AId::Transform, state);
46    let mut new_ts = Transform::default();
47
48    {
49        let x = node.convert_user_length(AId::X, &use_state, Length::zero());
50        let y = node.convert_user_length(AId::Y, &use_state, Length::zero());
51        new_ts = new_ts.pre_translate(x, y);
52    }
53
54    let linked_to_symbol = child.tag_name() == Some(EId::Symbol);
55
56    if linked_to_symbol {
57        if let Some(ts) = viewbox_transform(node, child, &use_state) {
58            new_ts = new_ts.pre_concat(ts);
59        }
60
61        if let Some(clip_rect) = get_clip_rect(node, child, &use_state) {
62            let mut g = clip_element(node, clip_rect, orig_ts, &use_state, cache);
63            g.abs_transform = parent.abs_transform;
64
65            // Make group for `use`.
66            if let Some(mut g2) =
67                converter::convert_group(node, &use_state, true, cache, &mut g, &|cache, g2| {
68                    convert_children(child, new_ts, &use_state, cache, false, g2);
69                })
70            {
71                // We must reset transform, because it was already set
72                // to the group with clip-path.
73                g.is_context_element = true;
74                g2.id = String::new(); // Prevent ID duplication.
75                g2.transform = Transform::default();
76                g.children.push(Node::Group(Box::new(g2)));
77            }
78
79            if g.children.is_empty() {
80                return;
81            }
82
83            g.calculate_bounding_boxes();
84            parent.children.push(Node::Group(Box::new(g)));
85            return;
86        }
87    }
88
89    orig_ts = orig_ts.pre_concat(new_ts);
90
91    if linked_to_symbol {
92        // Make group for `use`.
93        if let Some(mut g) =
94            converter::convert_group(node, &use_state, false, cache, parent, &|cache, g| {
95                convert_children(child, orig_ts, &use_state, cache, false, g);
96            })
97        {
98            g.is_context_element = true;
99            g.transform = Transform::default();
100            parent.children.push(Node::Group(Box::new(g)));
101        }
102    } else {
103        let linked_to_svg = child.tag_name() == Some(EId::Svg);
104        if linked_to_svg {
105            // When a `use` element references a `svg` element,
106            // we have to remember `use` element size and use it
107            // instead of `svg` element size.
108
109            let def = Length::new(100.0, LengthUnit::Percent);
110            // As per usual, the SVG spec doesn't clarify this edge case,
111            // but it seems like `use` size has to be reset by each `use`.
112            // Meaning if we have two nested `use` elements, where one had set `width` and
113            // other set `height`, we have to ignore the first `width`.
114            //
115            // Example:
116            // <use id="use1" xlink:href="#use2" width="100"/>
117            // <use id="use2" xlink:href="#svg2" height="100"/>
118            // <svg id="svg2" x="40" y="40" width="80" height="80" xmlns="http://www.w3.org/2000/svg"/>
119            //
120            // In this case `svg2` size is 80x100 and not 100x100.
121            use_state.use_size = (None, None);
122
123            // Width and height can be set independently.
124            if node.has_attribute(AId::Width) {
125                use_state.use_size.0 = Some(node.convert_user_length(AId::Width, &use_state, def));
126            }
127            if node.has_attribute(AId::Height) {
128                use_state.use_size.1 = Some(node.convert_user_length(AId::Height, &use_state, def));
129            }
130
131            convert_children(node, orig_ts, &use_state, cache, true, parent);
132        } else {
133            convert_children(node, orig_ts, &use_state, cache, true, parent);
134        }
135    }
136}
137
138pub(crate) fn convert_svg(
139    node: SvgNode,
140    state: &converter::State,
141    cache: &mut converter::Cache,
142    parent: &mut Group,
143) {
144    // We require original transformation to setup 'clipPath'.
145    let mut orig_ts = node.resolve_transform(AId::Transform, state);
146    let mut new_ts = Transform::default();
147
148    let x = node.convert_user_length(AId::X, state, Length::zero());
149    let y = node.convert_user_length(AId::Y, state, Length::zero());
150    new_ts = new_ts.pre_translate(x, y);
151
152    if let Some(ts) = viewbox_transform(node, node, state) {
153        new_ts = new_ts.pre_concat(ts);
154    }
155
156    // We have to create a new state which would have its viewBox set to the current SVG element.
157    // Note that we're not updating State::size - it's a completely different property.
158    let mut new_state = state.clone();
159    new_state.view_box = {
160        if let Some(vb) = node.parse_viewbox() {
161            vb
162        } else {
163            // No `viewBox` attribute? Then use `x`, `y`, `width` and `height` instead.
164            let (mut w, mut h) = use_node_size(node, &state);
165
166            // If attributes `width` and/or `height` are provided on the `use` element,
167            // then these values will override the corresponding attributes
168            // on the `svg` in the generated tree.
169            w = state.use_size.0.unwrap_or(w);
170            h = state.use_size.1.unwrap_or(h);
171
172            NonZeroRect::from_xywh(x, y, w, h).unwrap_or(state.view_box)
173        }
174    };
175
176    if let Some(clip_rect) = get_clip_rect(node, node, state) {
177        let mut g = clip_element(node, clip_rect, orig_ts, state, cache);
178        g.abs_transform = parent.abs_transform;
179        convert_children(node, new_ts, &new_state, cache, false, &mut g);
180        g.calculate_bounding_boxes();
181        parent.children.push(Node::Group(Box::new(g)));
182    } else {
183        orig_ts = orig_ts.pre_concat(new_ts);
184        convert_children(node, orig_ts, &new_state, cache, false, parent);
185    }
186}
187
188fn clip_element(
189    node: SvgNode,
190    clip_rect: NonZeroRect,
191    transform: Transform,
192    state: &converter::State,
193    cache: &mut converter::Cache,
194) -> Group {
195    // We can't set `clip-path` on the element itself,
196    // because it will be affected by a possible transform.
197    // So we have to create an additional group.
198
199    // Emulate a new viewport via clipPath.
200    //
201    // From:
202    // <defs/>
203    // <elem/>
204    //
205    // To:
206    // <defs>
207    //   <clipPath id="clipPath1">
208    //     <rect/>
209    //   </clipPath>
210    // </defs>
211    // <g clip-path="ulr(#clipPath1)">
212    //   <elem/>
213    // </g>
214
215    let mut clip_path = crate::ClipPath::empty(cache.gen_clip_path_id());
216
217    let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect(
218        clip_rect.to_rect(),
219    )))
220    .unwrap();
221    path.fill = Some(crate::Fill::default());
222    clip_path.root.children.push(Node::Path(Box::new(path)));
223
224    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.
225    let id = if state.parent_markers.is_empty() {
226        node.element_id().to_string()
227    } else {
228        String::new()
229    };
230
231    Group {
232        id,
233        transform,
234        clip_path: Some(Arc::new(clip_path)),
235        ..Group::empty()
236    }
237}
238
239fn convert_children(
240    node: SvgNode,
241    transform: Transform,
242    state: &converter::State,
243    cache: &mut converter::Cache,
244    is_context_element: bool,
245    parent: &mut Group,
246) {
247    // Temporarily adjust absolute transform so `convert_group` would account for `transform`.
248    let old_abs_transform = parent.abs_transform;
249    parent.abs_transform = parent.abs_transform.pre_concat(transform);
250
251    let required = !transform.is_identity();
252    if let Some(mut g) =
253        converter::convert_group(node, state, required, cache, parent, &|cache, g| {
254            if state.parent_clip_path.is_some() {
255                converter::convert_clip_path_elements(node, state, cache, g);
256            } else {
257                converter::convert_children(node, state, cache, g);
258            }
259        })
260    {
261        g.is_context_element = is_context_element;
262        g.transform = transform;
263        parent.children.push(Node::Group(Box::new(g)));
264    }
265
266    parent.abs_transform = old_abs_transform;
267}
268
269fn get_clip_rect(
270    use_node: SvgNode,
271    symbol_node: SvgNode,
272    state: &converter::State,
273) -> Option<NonZeroRect> {
274    // No need to clip elements with overflow:visible.
275    if matches!(
276        symbol_node.attribute(AId::Overflow),
277        Some("visible") | Some("auto")
278    ) {
279        return None;
280    }
281
282    // A nested `svg` with only the `viewBox` attribute and no "rectangle" (x, y, width, height)
283    // should not be clipped.
284    if use_node.tag_name() == Some(EId::Svg) {
285        // Nested `svg` referenced by `use` still should be clipped, but by `use` bounds.
286        if state.use_size.0.is_none() && state.use_size.1.is_none() {
287            if !(use_node.has_attribute(AId::Width) && use_node.has_attribute(AId::Height)) {
288                return None;
289            }
290        }
291    }
292
293    let (x, y, mut w, mut h) = {
294        let x = use_node.convert_user_length(AId::X, state, Length::zero());
295        let y = use_node.convert_user_length(AId::Y, state, Length::zero());
296        let (w, h) = use_node_size(use_node, state);
297        (x, y, w, h)
298    };
299
300    if use_node.tag_name() == Some(EId::Svg) {
301        // If attributes `width` and/or `height` are provided on the `use` element,
302        // then these values will override the corresponding attributes
303        // on the `svg` in the generated tree.
304        w = state.use_size.0.unwrap_or(w);
305        h = state.use_size.1.unwrap_or(h);
306    }
307
308    if !w.is_valid_length() || !h.is_valid_length() {
309        return None;
310    }
311
312    NonZeroRect::from_xywh(x, y, w, h)
313}
314
315fn use_node_size(node: SvgNode, state: &converter::State) -> (f32, f32) {
316    let def = Length::new(100.0, LengthUnit::Percent);
317    let w = node.convert_user_length(AId::Width, state, def);
318    let h = node.convert_user_length(AId::Height, state, def);
319    (w, h)
320}
321
322fn viewbox_transform(
323    node: SvgNode,
324    linked: SvgNode,
325    state: &converter::State,
326) -> Option<Transform> {
327    let (mut w, mut h) = use_node_size(node, state);
328
329    if node.tag_name() == Some(EId::Svg) {
330        // If attributes `width` and/or `height` are provided on the `use` element,
331        // then these values will override the corresponding attributes
332        // on the `svg` in the generated tree.
333        w = state.use_size.0.unwrap_or(w);
334        h = state.use_size.1.unwrap_or(h);
335    }
336
337    let size = Size::from_wh(w, h)?;
338    let rect = linked.parse_viewbox()?;
339    let aspect = linked
340        .attribute(AId::PreserveAspectRatio)
341        .unwrap_or_default();
342    let view_box = ViewBox { rect, aspect };
343
344    Some(view_box.to_transform(size))
345}