use crate::plugins::Plugin;
use crate::tree::{Document, Element, Node};
pub struct ConvertShapeToPath;
impl Plugin for ConvertShapeToPath {
fn apply(&self, doc: &mut Document) {
convert_shapes_in_nodes(&mut doc.root);
}
}
fn convert_shapes_in_nodes(nodes: &mut Vec<Node>) {
for node in nodes {
if let Node::Element(elem) = node {
convert_element(elem);
convert_shapes_in_nodes(&mut elem.children);
}
}
}
fn convert_element(elem: &mut Element) {
let d = match elem.name.as_str() {
"rect" => convert_rect(elem),
"circle" => convert_circle(elem),
"ellipse" => convert_ellipse(elem),
"line" => convert_line(elem),
"polygon" => convert_poly(elem, true),
"polyline" => convert_poly(elem, false),
_ => None,
};
if let Some(path_data) = d {
elem.name = "path".to_string();
remove_shape_attrs(elem);
elem.attributes.insert("d".to_string(), path_data);
}
}
fn get_num(elem: &Element, attr: &str, def: f64) -> f64 {
elem.attributes
.get(attr)
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(def)
}
fn convert_rect(elem: &Element) -> Option<String> {
let w = get_num(elem, "width", 0.0);
let h = get_num(elem, "height", 0.0);
let x = get_num(elem, "x", 0.0);
let y = get_num(elem, "y", 0.0);
let rx_raw = elem
.attributes
.get("rx")
.and_then(|v| v.parse::<f64>().ok());
let ry_raw = elem
.attributes
.get("ry")
.and_then(|v| v.parse::<f64>().ok());
if w == 0.0 || h == 0.0 {
return None; }
if rx_raw.is_none() && ry_raw.is_none() {
return Some(format!("M{} {}h{}v{}h-{}z", x, y, w, h, w));
}
None
}
fn convert_circle(elem: &Element) -> Option<String> {
let r = get_num(elem, "r", 0.0);
if r <= 0.0 {
return None;
}
let cx = get_num(elem, "cx", 0.0);
let cy = get_num(elem, "cy", 0.0);
Some(format!(
"M{} {}A{} {} 0 1 0 {} {}A{} {} 0 1 0 {} {}z",
cx - r,
cy,
r,
r,
cx + r,
cy,
r,
r,
cx - r,
cy
))
}
fn convert_ellipse(elem: &Element) -> Option<String> {
let rx = get_num(elem, "rx", 0.0);
let ry = get_num(elem, "ry", 0.0);
if rx <= 0.0 || ry <= 0.0 {
return None;
}
let cx = get_num(elem, "cx", 0.0);
let cy = get_num(elem, "cy", 0.0);
Some(format!(
"M{} {}A{} {} 0 1 0 {} {}A{} {} 0 1 0 {} {}z",
cx - rx,
cy,
rx,
ry,
cx + rx,
cy,
rx,
ry,
cx - rx,
cy
))
}
fn convert_line(elem: &Element) -> Option<String> {
let x1 = get_num(elem, "x1", 0.0);
let y1 = get_num(elem, "y1", 0.0);
let x2 = get_num(elem, "x2", 0.0);
let y2 = get_num(elem, "y2", 0.0);
Some(format!("M{} {}L{} {}", x1, y1, x2, y2))
}
fn convert_poly(elem: &Element, close: bool) -> Option<String> {
let points = elem.attributes.get("points")?;
let points_clean = points.replace(',', " ");
let coords: Vec<&str> = points_clean.split_whitespace().collect();
if coords.len() < 2 {
return None;
}
let mut d = String::from("M");
if coords.len() >= 2 {
d.push_str(coords[0]);
d.push(' ');
d.push_str(coords[1]);
for i in (2..coords.len()).step_by(2) {
if i + 1 < coords.len() {
d.push_str("L");
d.push_str(coords[i]);
d.push(' ');
d.push_str(coords[i + 1]);
}
}
}
if close {
d.push('z');
}
Some(d)
}
fn remove_shape_attrs(elem: &mut Element) {
let attrs = [
"x", "y", "width", "height", "rx", "ry", "r", "cx", "cy", "x1", "y1", "x2", "y2", "points",
];
for attr in attrs {
elem.attributes.shift_remove(attr);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::printer;
#[test]
fn test_rect_to_path() {
let input = "<svg><rect x=\"10\" y=\"20\" width=\"100\" height=\"50\"/></svg>";
let expected = "<svg><path d=\"M10 20h100v50h-100z\"/></svg>";
let mut doc = parser::parse(input).unwrap();
ConvertShapeToPath.apply(&mut doc);
assert_eq!(printer::print(&doc), expected);
}
#[test]
fn test_circle_to_path() {
let input = "<svg><circle cx=\"50\" cy=\"50\" r=\"50\"/></svg>";
let expected = "<svg><path d=\"M0 50A50 50 0 1 0 100 50A50 50 0 1 0 0 50z\"/></svg>";
let mut doc = parser::parse(input).unwrap();
ConvertShapeToPath.apply(&mut doc);
assert_eq!(printer::print(&doc), expected);
}
#[test]
fn test_line_to_path() {
let input = "<svg><line x1=\"10\" y1=\"10\" x2=\"50\" y2=\"50\"/></svg>";
let expected = "<svg><path d=\"M10 10L50 50\"/></svg>";
let mut doc = parser::parse(input).unwrap();
ConvertShapeToPath.apply(&mut doc);
assert_eq!(printer::print(&doc), expected);
}
#[test]
fn test_polygon_to_path() {
let input = "<svg><polygon points=\"0,0 100,0 100,100\"/></svg>";
let mut doc = parser::parse(input).unwrap();
ConvertShapeToPath.apply(&mut doc);
let out = printer::print(&doc);
assert!(out.contains("path"));
assert!(out.contains("d=\"M0 0L100 0L100 100z"));
}
}