use crate::plugins::Plugin;
use crate::tree::{Document, Node};
pub struct MergePaths;
impl Plugin for MergePaths {
fn apply(&self, doc: &mut Document) {
merge_paths_in_nodes(&mut doc.root);
}
}
fn merge_paths_in_nodes(nodes: &mut Vec<Node>) {
for node in nodes.iter_mut() {
if let Node::Element(elem) = node {
merge_paths_in_nodes(&mut elem.children);
}
}
let mut i = 0;
while i < nodes.len() {
let can_merge = if i + 1 < nodes.len() {
match (&nodes[i], &nodes[i + 1]) {
(Node::Element(e1), Node::Element(e2)) => {
e1.name == "path" && e2.name == "path" && are_attrs_equal(e1, e2)
}
_ => false,
}
} else {
false
};
if can_merge {
let d2 = if let Node::Element(e2) = nodes.remove(i + 1) {
e2.attributes.get("d").cloned().unwrap_or_default()
} else {
unreachable!()
};
if let Node::Element(e1) = &mut nodes[i] {
if let Some(d1) = e1.attributes.get_mut("d") {
d1.push(' ');
d1.push_str(&d2);
}
}
} else {
i += 1;
}
}
}
fn are_attrs_equal(e1: &crate::tree::Element, e2: &crate::tree::Element) -> bool {
if e1.attributes.len() != e2.attributes.len() {
return false;
}
for (k, v1) in &e1.attributes {
if k == "d" {
continue;
}
if let Some(v2) = e2.attributes.get(k) {
if v1 != v2 {
return false;
}
} else {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::printer;
#[test]
fn test_merge_paths() {
let input = "<svg><path d=\"M0 0L10 10\" fill=\"red\"/><path d=\"M20 20L30 30\" fill=\"red\"/></svg>";
let expected_d = "M0 0L10 10 M20 20L30 30";
let mut doc = parser::parse(input).unwrap();
MergePaths.apply(&mut doc);
let out = printer::print(&doc);
assert!(out.contains(expected_d));
assert_eq!(out.matches("<path").count(), 1);
}
#[test]
fn test_no_merge_diff_attrs() {
let input = "<svg><path d=\"M0 0\" fill=\"red\"/><path d=\"M10 10\" fill=\"blue\"/></svg>";
let mut doc = parser::parse(input).unwrap();
MergePaths.apply(&mut doc);
let out = printer::print(&doc);
assert_eq!(out.matches("<path").count(), 2);
}
}