use crate::view_box::ViewBox;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Svg<'a> {
pub xmlns: &'a str,
pub view_box: ViewBox,
pub body: &'a str,
}
impl<'a> std::fmt::Display for Svg<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"<svg xmlns=\"{}\" viewBox=\"{}\">",
self.xmlns, self.view_box
)?;
f.write_str(self.body)?;
f.write_str("</svg>")?;
Ok(())
}
}
#[derive(Debug)]
pub struct SvgParseError(pub &'static str);
impl<'a> Svg<'a> {
pub fn new(svg_content: &'a str) -> Result<Self, SvgParseError> {
let xmlns = extract_attr(svg_content, "xmlns")?;
let (min_x, min_y, width, height) = parse_view_box(extract_attr(svg_content, "viewBox")?)?;
let body = extract_body(svg_content)?;
Ok(Self {
xmlns,
view_box: ViewBox {
min_x,
min_y,
width,
height,
},
body,
})
}
}
fn extract_attr<'a>(input: &'a str, attr: &str) -> Result<&'a str, SvgParseError> {
let needle = format!(r#"{attr}=""#);
let start = input
.find(&needle)
.ok_or(SvgParseError("attribute not found"))?
+ needle.len();
let rest = &input[start..];
let end = rest
.find('"')
.ok_or(SvgParseError("attribute not terminated"))?;
Ok(&rest[..end])
}
fn extract_body(input: &str) -> Result<&str, SvgParseError> {
let start = input.find('>').ok_or(SvgParseError("missing >"))? + 1;
let end = input
.rfind("</svg>")
.ok_or(SvgParseError("missing </svg>"))?;
Ok(&input[start..end])
}
fn parse_view_box(input: &str) -> Result<(f32, f32, f32, f32), SvgParseError> {
let mut parts = input.split_whitespace();
let a = parts
.next()
.ok_or(SvgParseError("missing viewBox[0]"))?
.parse()
.map_err(|_| SvgParseError("invalid float"))?;
let b = parts
.next()
.ok_or(SvgParseError("missing viewBox[1]"))?
.parse()
.map_err(|_| SvgParseError("invalid float"))?;
let c = parts
.next()
.ok_or(SvgParseError("missing viewBox[2]"))?
.parse()
.map_err(|_| SvgParseError("invalid float"))?;
let d = parts
.next()
.ok_or(SvgParseError("missing viewBox[3]"))?
.parse()
.map_err(|_| SvgParseError("invalid float"))?;
Ok((a, b, c, d))
}