svgcleaner/task/
regroup_gradient_stops.rs

1// svgcleaner could help you to clean up your SVG files
2// from unnecessary data.
3// Copyright (C) 2012-2018 Evgeniy Reizner
4//
5// This program is free software; you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation; either version 2 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License along
16// with this program; if not, write to the Free Software Foundation, Inc.,
17// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19use svgdom::{
20    Document,
21    ElementType,
22    Node,
23};
24
25use task::short::{EId, AId};
26
27pub fn regroup_gradient_stops(doc: &mut Document) {
28    let mut nodes: Vec<Node> = doc.descendants()
29        .filter(|n| n.is_gradient())
30        .filter(|n| n.has_children())
31        .filter(|n| !n.has_attribute(AId::XlinkHref))
32        .collect();
33
34    let mut is_changed = false;
35    let mut join_nodes = Vec::new();
36
37    // TODO: join with rm_dupl_defs::rm_loop
38    let mut i1 = 0;
39    while i1 < nodes.len() {
40        let mut node1 = nodes[i1].clone();
41
42        let mut i2 = i1 + 1;
43        while i2 < nodes.len() {
44            let node2 = nodes[i2].clone();
45            i2 += 1;
46
47            if super::rm_dupl_defs::is_equal_stops(&node1, &node2) {
48                join_nodes.push(node2.clone());
49
50                nodes.remove(i2 - 1);
51                i2 -= 1;
52            }
53        }
54
55        if !join_nodes.is_empty() {
56            is_changed = true;
57
58            let mut new_lg = doc.create_element(EId::LinearGradient);
59            let new_id = gen_id(doc, "lg");
60            new_lg.set_id(new_id);
61
62            while node1.has_children() {
63                let mut c = node1.first_child().unwrap();
64                c.detach();
65                new_lg.append(&c);
66            }
67            node1.set_attribute((AId::XlinkHref, new_lg.clone()));
68
69            node1.insert_before(&new_lg);
70
71            for jn in &mut join_nodes {
72                while jn.has_children() {
73                    let mut c = jn.first_child().unwrap();
74                    c.remove();
75                }
76                jn.set_attribute((AId::XlinkHref, new_lg.clone()));
77            }
78
79            join_nodes.clear();
80        }
81
82        i1 += 1;
83    }
84
85    if is_changed {
86        // We must resolve attributes for gradients created above.
87        super::resolve_linear_gradient_attributes(doc);
88    }
89}
90
91fn gen_id(doc: &Document, prefix: &str) -> String {
92    let mut n = 1;
93
94    let mut s = String::new();
95    loop {
96        s.clear();
97        s.push_str(prefix);
98        s.push_str(&n.to_string());
99
100        // TODO: very slow
101        if !doc.descendants().any(|n| *n.id() == s) {
102            break;
103        }
104
105        n += 1;
106    }
107
108    s
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use svgdom::{Document, ToStringWithOptions};
115    use task;
116
117    macro_rules! test {
118        ($name:ident, $in_text:expr, $out_text:expr) => (
119            #[test]
120            fn $name() {
121                let mut doc = Document::from_str($in_text).unwrap();
122                task::resolve_linear_gradient_attributes(&doc);
123                task::resolve_radial_gradient_attributes(&doc);
124                task::resolve_stop_attributes(&doc).unwrap();
125                regroup_gradient_stops(&mut doc);
126                assert_eq_text!(doc.to_string_with_opt(&write_opt_for_tests!()), $out_text);
127            }
128        )
129    }
130
131    test!(rm_1,
132"<svg>
133    <linearGradient id='lg1' x1='50'>
134        <stop offset='0'/>
135        <stop offset='1'/>
136    </linearGradient>
137    <linearGradient id='lg2' x1='100'>
138        <stop offset='0'/>
139        <stop offset='1'/>
140    </linearGradient>
141</svg>",
142"<svg>
143    <linearGradient id='lg3'>
144        <stop offset='0'/>
145        <stop offset='1'/>
146    </linearGradient>
147    <linearGradient id='lg1' x1='50' xlink:href='#lg3'/>
148    <linearGradient id='lg2' x1='100' xlink:href='#lg3'/>
149</svg>
150");
151
152    test!(rm_2,
153"<svg>
154    <linearGradient id='lg1' x1='50'>
155        <stop offset='0'/>
156        <stop offset='1'/>
157    </linearGradient>
158    <linearGradient id='lg3' x1='50'>
159        <stop offset='0.5'/>
160        <stop offset='1'/>
161    </linearGradient>
162    <linearGradient id='lg2' x1='100'>
163        <stop offset='0'/>
164        <stop offset='1'/>
165    </linearGradient>
166    <linearGradient id='lg4' x1='100'>
167        <stop offset='0.5'/>
168        <stop offset='1'/>
169    </linearGradient>
170</svg>",
171"<svg>
172    <linearGradient id='lg5'>
173        <stop offset='0'/>
174        <stop offset='1'/>
175    </linearGradient>
176    <linearGradient id='lg1' x1='50' xlink:href='#lg5'/>
177    <linearGradient id='lg6'>
178        <stop offset='0.5'/>
179        <stop offset='1'/>
180    </linearGradient>
181    <linearGradient id='lg3' x1='50' xlink:href='#lg6'/>
182    <linearGradient id='lg2' x1='100' xlink:href='#lg5'/>
183    <linearGradient id='lg4' x1='100' xlink:href='#lg6'/>
184</svg>
185");
186
187}