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
use crate::plugins::Plugin;
use crate::tree::{Document, Node};
pub struct MoveGroupAttrsToElems;
impl Plugin for MoveGroupAttrsToElems {
fn apply(&self, doc: &mut Document) {
process_nodes(&mut doc.root);
}
}
fn process_nodes(nodes: &mut Vec<Node>) {
// We need to iterate and modify.
// If we modify a group, we might need to process its children again?
// Let's do a recursive traversal.
for node in nodes {
if let Node::Element(elem) = node {
if elem.name == "g" {
// Try to move attributes to children
if !elem.children.is_empty() {
let cached_transform = elem.attributes.get("transform").cloned();
// inheritable attributes
// If we move them, we remove them from group.
// But we can only move if ALL children can accept them?
// Or we just push them down?
// SVGO logic: if group has nothing else, we can push down.
// This is complex. Let's strictly implement Transform propagation for now as it solves the "unwrappable group" issue.
if let Some(ref root_transform) = cached_transform {
for child in &mut elem.children {
if let Node::Element(child_elem) = child {
// Prepend group transform to child transform
if let Some(child_t) = child_elem.attributes.get_mut("transform") {
*child_t = format!("{} {}", root_transform, child_t);
} else {
child_elem
.attributes
.insert("transform".to_string(), root_transform.clone());
}
}
}
// Remove from group
elem.attributes.shift_remove("transform");
}
}
}
process_nodes(&mut elem.children);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::printer;
#[test]
fn test_move_transform() {
let input = "<svg><g transform=\"scale(2)\"><rect width=\"10\"/><circle/></g></svg>";
let expected = "<svg><g><rect width=\"10\" transform=\"scale(2)\"/><circle transform=\"scale(2)\"/></g></svg>";
// Note: CollapseGroups would later remove the empty <g>
let mut doc = parser::parse(input).unwrap();
MoveGroupAttrsToElems.apply(&mut doc);
assert_eq!(printer::print(&doc), expected);
}
#[test]
fn test_concat_transform() {
let input = "<svg><g transform=\"translate(10)\"><rect transform=\"scale(2)\"/></g></svg>";
let expected = "<svg><g><rect transform=\"translate(10) scale(2)\"/></g></svg>";
let mut doc = parser::parse(input).unwrap();
MoveGroupAttrsToElems.apply(&mut doc);
assert_eq!(printer::print(&doc), expected);
}
}