1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use svgdom::{
Length,
Transform,
};
use super::prelude::*;
/// Resolves default `use` attributes.
pub fn resolve_use_attributes(doc: &Document) {
for mut node in doc.root().descendants().filter(|n| n.is_tag_name(EId::Use)) {
node.set_attribute_if_none((AId::X, 0.0));
node.set_attribute_if_none((AId::Y, 0.0));
node.set_attribute_if_none((AId::Width, Length::new(100.0, Unit::Percent)));
node.set_attribute_if_none((AId::Height, Length::new(100.0, Unit::Percent)));
}
}
pub fn resolve_use(doc: &mut Document, opt: &Options) {
let mut rm_nodes = Vec::new();
// 'use' elements can be linked in any order,
// so we have to process the tree until all 'use' are solved.
let mut is_any_resolved = true;
while is_any_resolved {
is_any_resolved = false;
rm_nodes.clear();
for mut node in doc.root().descendants().filter(|n| n.is_tag_name(EId::Use)) {
let av = node.attributes().get_value(AId::Href).cloned();
if let Some(AValue::Link(mut link)) = av {
// Ignore 'use' elements linked to other 'use' elements.
if link.is_tag_name(EId::Use) {
continue;
}
// TODO: this
// We don't support 'use' elements linked to 'svg' element.
if link.is_tag_name(EId::Svg) {
warn!("'use' element linked to an 'svg' element is not supported. Skipped.");
rm_nodes.push(node.clone());
continue;
}
// Check that none of the linked node's children reference current `use` node
// via other `use` node.
//
// Example:
// <g id="g1">
// <use xlink:href="#use1" id="use2"/>
// </g>
// <use xlink:href="#g1" id="use1"/>
//
// `use2` should be removed.
let mut is_recursive = false;
for link_child in link.descendants().skip(1).filter(|n| n.is_tag_name(EId::Use)) {
let av = link_child.attributes().get_value(AId::Href).cloned();
if let Some(AValue::Link(link2)) = av {
if link2 == node {
is_recursive = true;
break;
}
}
}
if is_recursive {
warn!("Recursive 'use' detected. '{}' will be deleted.", node.id());
rm_nodes.push(node.clone());
continue;
}
_resolve_use(doc, &mut node, &mut link, opt);
is_any_resolved = true;
}
// 'use' elements without 'xlink:href' attribute will be removed
// by 'remove_invisible_elements()'.
}
// Remove unresolved 'use' elements, since there is not need
// to keep them around and they will be skipped anyway.
for node in &mut rm_nodes {
doc.remove_node(node.clone());
}
}
}
fn _resolve_use(
doc: &mut Document,
use_node: &mut Node,
linked_node: &mut Node,
opt: &Options,
) {
// Unlink 'use'.
use_node.remove_attribute(AId::Href);
use_node.set_tag_name(EId::G);
// Remember that this group was 'use' before.
use_node.set_attribute(("usvg-group", 1));
// We require original transformation to setup 'clipPath'.
let orig_ts = use_node.attributes().get_transform(AId::Transform).unwrap_or_default();
// Remove original transform. It will be resolved later.
use_node.remove_attribute(AId::Transform);
{
// If the `use` element has a non-zero `x` or `y` attributes
// then we should add their values to
// the transform (existing or default).
let x = use_node.attributes().get_number_or(AId::X, 0.0);
let y = use_node.attributes().get_number_or(AId::Y, 0.0);
if !(x.is_fuzzy_zero() && y.is_fuzzy_zero()) {
use_node.append_transform(Transform::new_translate(x, y));
}
}
// TODO: validate linked nodes
if linked_node.is_tag_name(EId::Symbol) {
resolve_symbol(doc, use_node, linked_node, orig_ts)
} else {
let new_node = doc.copy_node_deep(linked_node.clone());
use_node.append(new_node);
use_node.prepend_transform(orig_ts);
}
// Remove unneeded attributes.
use_node.remove_attribute(AId::X);
use_node.remove_attribute(AId::Y);
use_node.remove_attribute(AId::Width);
use_node.remove_attribute(AId::Height);
for mut node in use_node.descendants().skip(1) {
if node.has_attribute("resolved-font-size") {
let parent = node.parent().unwrap_or(use_node.clone());
let fs = parent.find_attribute(AId::FontSize).unwrap_or(opt.font_size);
node.set_attribute((AId::FontSize, fs));
}
}
}
fn resolve_symbol(
doc: &mut Document,
use_node: &mut Node,
linked_node: &mut Node,
orig_ts: Transform,
) {
// Required for the 'clip_element' method.
linked_node.copy_attribute_to(AId::Overflow, use_node);
if linked_node.has_attribute(AId::ViewBox) {
use_node.copy_attribute_to(AId::Width, linked_node);
use_node.copy_attribute_to(AId::Height, linked_node);
if let Some(ts) = linked_node.get_viewbox_transform() {
use_node.append_transform(ts);
}
}
let new_node = doc.copy_node_deep(linked_node.clone());
for child in new_node.children() {
use_node.append(child);
}
let new_g_node = super::clip_element::clip_element(doc, use_node);
if let Some(mut g_node) = new_g_node {
// If 'clipPath' was created we have to set the original transform
// to the group that contains 'clip-path' attribute.
g_node.set_attribute((AId::Transform, orig_ts));
} else {
// Set the original transform back to the 'use' itself.
use_node.prepend_transform(orig_ts);
}
}