icons 0.6.0

Icons for Rust fullstack applications โ€” Leptos and Dioxus.
Documentation
use dioxus::prelude::*;

// * ๐Ÿงช cargo test --package icons --lib --features dioxus

#[derive(Debug, Clone)]
pub enum SvgElement {
    Path { d: String },
    Circle { cx: String, cy: String, r: String },
    Rect { x: String, y: String, width: String, height: String, rx: Option<String>, ry: Option<String> },
    Ellipse { cx: String, cy: String, rx: String, ry: String },
    Line { x1: String, y1: String, x2: String, y2: String },
    Polyline { points: String },
    Polygon { points: String },
    // Add more SVG elements as needed (g, defs, etc.)
}

impl SvgElement {
    pub fn to_dioxus_element(&self) -> Element {
        match self {
            SvgElement::Path { d } => rsx! { path { d: "{d}" } },
            SvgElement::Circle { cx, cy, r } => rsx! { circle { cx: "{cx}", cy: "{cy}", r: "{r}" } },
            SvgElement::Rect { x, y, width, height, rx, ry } => match (rx, ry) {
                (Some(rx), Some(ry)) => rsx! { rect { x: "{x}", y: "{y}", width: "{width}", height: "{height}", rx: "{rx}", ry: "{ry}" } },
                (Some(rx), None) => rsx! { rect { x: "{x}", y: "{y}", width: "{width}", height: "{height}", rx: "{rx}" } },
                (None, Some(ry)) => rsx! { rect { x: "{x}", y: "{y}", width: "{width}", height: "{height}", ry: "{ry}" } },
                (None, None) => rsx! { rect { x: "{x}", y: "{y}", width: "{width}", height: "{height}" } },
            },
            SvgElement::Ellipse { cx, cy, rx, ry } => rsx! { ellipse { cx: "{cx}", cy: "{cy}", rx: "{rx}", ry: "{ry}" } },
            SvgElement::Line { x1, y1, x2, y2 } => rsx! { line { x1: "{x1}", y1: "{y1}", x2: "{x2}", y2: "{y2}" } },
            SvgElement::Polyline { points } => rsx! { polyline { points: "{points}" } },
            SvgElement::Polygon { points } => rsx! { polygon { points: "{points}" } },
        }
    }
}

// Helper function to parse SVG content and return structured elements
pub fn parse_svg_elements(svg_content: &str) -> Vec<SvgElement> {
    svg_content
        .lines()
        .filter_map(|line| {
            let line = line.trim();
            if line.is_empty() {
                return None;
            }

            // Parse path, circle, rect, ellipse, line, polyline, and polygon elements
            if line.starts_with("<path ") {
                parse_path_element(line)
            } else if line.starts_with("<circle ") {
                parse_circle_element(line)
            } else if line.starts_with("<rect ") {
                parse_rect_element(line)
            } else if line.starts_with("<ellipse ") {
                parse_ellipse_element(line)
            } else if line.starts_with("<line ") {
                parse_line_element(line)
            } else if line.starts_with("<polyline ") {
                parse_polyline_element(line)
            } else if line.starts_with("<polygon ") {
                parse_polygon_element(line)
            } else {
                None
            }
        })
        .collect()
}

// Helper function to extract attributes from SVG element strings
fn extract_attribute(line: &str, attr: &str) -> Option<String> {
    if let Some(start) = line.find(&format!("{}=\"", attr)) {
        let start = start + attr.len() + 2; // Skip 'attr="'
        if let Some(end) = line[start..].find('"') {
            return Some(line[start..start + end].to_string());
        }
    }
    None
}

// Specific parsers for path, circle, rect, ellipse, line, polyline, and polygon elements
fn parse_path_element(line: &str) -> Option<SvgElement> {
    extract_attribute(line, "d").map(|d| SvgElement::Path { d })
}

fn parse_circle_element(line: &str) -> Option<SvgElement> {
    let cx = extract_attribute(line, "cx")?;
    let cy = extract_attribute(line, "cy")?;
    let r = extract_attribute(line, "r")?;
    Some(SvgElement::Circle { cx, cy, r })
}

fn parse_rect_element(line: &str) -> Option<SvgElement> {
    let x = extract_attribute(line, "x")?;
    let y = extract_attribute(line, "y")?;
    let width = extract_attribute(line, "width")?;
    let height = extract_attribute(line, "height")?;
    let rx = extract_attribute(line, "rx");
    let ry = extract_attribute(line, "ry");
    Some(SvgElement::Rect { x, y, width, height, rx, ry })
}

fn parse_ellipse_element(line: &str) -> Option<SvgElement> {
    let cx = extract_attribute(line, "cx")?;
    let cy = extract_attribute(line, "cy")?;
    let rx = extract_attribute(line, "rx")?;
    let ry = extract_attribute(line, "ry")?;
    Some(SvgElement::Ellipse { cx, cy, rx, ry })
}

fn parse_line_element(line: &str) -> Option<SvgElement> {
    let x1 = extract_attribute(line, "x1")?;
    let y1 = extract_attribute(line, "y1")?;
    let x2 = extract_attribute(line, "x2")?;
    let y2 = extract_attribute(line, "y2")?;
    Some(SvgElement::Line { x1, y1, x2, y2 })
}

fn parse_polyline_element(line: &str) -> Option<SvgElement> {
    let points = extract_attribute(line, "points")?;
    Some(SvgElement::Polyline { points })
}

fn parse_polygon_element(line: &str) -> Option<SvgElement> {
    let points = extract_attribute(line, "points")?;
    Some(SvgElement::Polygon { points })
}

/*ยด:ยฐโ€ข.ยฐ+.*โ€ขยด.*:หš.ยฐ*.หšโ€ขยด.ยฐ:ยฐโ€ข.ยฐโ€ข.*โ€ขยด.*:หš.ยฐ*.หšโ€ขยด.ยฐ:ยฐโ€ข.ยฐ+.*โ€ขยด.*:*/
/*                        ๐Ÿงช TESTS ๐Ÿงช                         */
/*.โ€ขยฐ:ยฐ.ยด+หš.*ยฐ.หš:*.ยดโ€ข*.+ยฐ.โ€ขยฐ:ยด*.ยดโ€ข*.โ€ขยฐ.โ€ขยฐ:ยฐ.ยด:โ€ขหšยฐ.*ยฐ.หš:*.ยด+ยฐ.โ€ข*/

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_svg_element_path_creation() {
        let path = SvgElement::Path { d: "M10 10 L20 20".to_string() };

        match path {
            SvgElement::Path { d } => assert_eq!(d, "M10 10 L20 20"),
            _ => panic!("Expected Path variant"),
        }
    }

    #[test]
    fn test_svg_element_circle_creation() {
        let circle = SvgElement::Circle { cx: "10".to_string(), cy: "20".to_string(), r: "5".to_string() };

        match circle {
            SvgElement::Circle { cx, cy, r } => {
                assert_eq!(cx, "10");
                assert_eq!(cy, "20");
                assert_eq!(r, "5");
            }
            _ => panic!("Expected Circle variant"),
        }
    }

    #[test]
    fn test_extract_attribute_success() {
        let line = r#"<path d="M10 10 L20 20" fill="red" />"#;
        assert_eq!(extract_attribute(line, "d"), Some("M10 10 L20 20".to_string()));
        assert_eq!(extract_attribute(line, "fill"), Some("red".to_string()));
    }

    #[test]
    fn test_extract_attribute_not_found() {
        let line = r#"<path d="M10 10 L20 20" />"#;
        assert_eq!(extract_attribute(line, "fill"), None);
        assert_eq!(extract_attribute(line, "stroke"), None);
    }

    #[test]
    fn test_extract_attribute_empty_value() {
        let line = r#"<path d="" />"#;
        assert_eq!(extract_attribute(line, "d"), Some("".to_string()));
    }

    #[test]
    fn test_parse_path_element_success() {
        let line = r#"<path d="M10 10 L20 20" />"#;
        let result = parse_path_element(line);
        assert!(result.is_some());

        if let Some(SvgElement::Path { d }) = result {
            assert_eq!(d, "M10 10 L20 20");
        } else {
            panic!("Expected Path element");
        }
    }

    #[test]
    fn test_parse_path_element_missing_d() {
        let line = r#"<path fill="red" />"#;
        let result = parse_path_element(line);
        assert!(result.is_none());
    }

    #[test]
    fn test_parse_circle_element_success() {
        let line = r#"<circle cx="10" cy="20" r="5" />"#;
        let result = parse_circle_element(line);
        assert!(result.is_some());

        if let Some(SvgElement::Circle { cx, cy, r }) = result {
            assert_eq!(cx, "10");
            assert_eq!(cy, "20");
            assert_eq!(r, "5");
        } else {
            panic!("Expected Circle element");
        }
    }

    #[test]
    fn test_parse_circle_element_missing_attributes() {
        let line = r#"<circle cx="10" cy="20" />"#; // Missing r
        let result = parse_circle_element(line);
        assert!(result.is_none());
    }

    #[test]
    fn test_parse_rect_element_success() {
        let line = r#"<rect x="10" y="20" width="30" height="40" />"#;
        let result = parse_rect_element(line);
        assert!(result.is_some());

        if let Some(SvgElement::Rect { x, y, width, height, rx, ry }) = result {
            assert_eq!(x, "10");
            assert_eq!(y, "20");
            assert_eq!(width, "30");
            assert_eq!(height, "40");
            assert_eq!(rx, None);
            assert_eq!(ry, None);
        } else {
            panic!("Expected Rect element");
        }
    }

    #[test]
    fn test_parse_rect_element_with_rx_ry() {
        let line = r#"<rect x="10" y="20" width="30" height="40" rx="5" ry="3" />"#;
        let result = parse_rect_element(line);
        assert!(result.is_some());

        if let Some(SvgElement::Rect { x, y, width, height, rx, ry }) = result {
            assert_eq!(x, "10");
            assert_eq!(y, "20");
            assert_eq!(width, "30");
            assert_eq!(height, "40");
            assert_eq!(rx, Some("5".to_string()));
            assert_eq!(ry, Some("3".to_string()));
        } else {
            panic!("Expected Rect element");
        }
    }

    #[test]
    fn test_parse_svg_elements_multiple() {
        let svg_content = r#"<path d="M10 10 L20 20" />
<circle cx="10" cy="20" r="5" />
<rect x="0" y="0" width="100" height="50" />"#;

        let elements = parse_svg_elements(svg_content);
        assert_eq!(elements.len(), 3);

        match &elements[0] {
            SvgElement::Path { d } => assert_eq!(d, "M10 10 L20 20"),
            _ => panic!("Expected first element to be Path"),
        }

        match &elements[1] {
            SvgElement::Circle { cx, cy, r } => {
                assert_eq!(cx, "10");
                assert_eq!(cy, "20");
                assert_eq!(r, "5");
            }
            _ => panic!("Expected second element to be Circle"),
        }

        match &elements[2] {
            SvgElement::Rect { x, y, width, height, .. } => {
                assert_eq!(x, "0");
                assert_eq!(y, "0");
                assert_eq!(width, "100");
                assert_eq!(height, "50");
            }
            _ => panic!("Expected third element to be Rect"),
        }
    }

    #[test]
    fn test_parse_svg_elements_empty_lines() {
        let svg_content = r#"

<path d="M10 10 L20 20" />

<circle cx="10" cy="20" r="5" />

"#;

        let elements = parse_svg_elements(svg_content);
        assert_eq!(elements.len(), 2);
    }

    #[test]
    fn test_parse_svg_elements_unsupported_element() {
        let svg_content = r#"<path d="M10 10 L20 20" />
<unsupported attr="value" />
<circle cx="10" cy="20" r="5" />"#;

        let elements = parse_svg_elements(svg_content);
        assert_eq!(elements.len(), 2); // Only path and circle, unsupported ignored
    }
}