use crate::image::format::{DecodedImage, ImageData, ImageFormat};
pub fn rasterize_svg(
svg_data: &[u8],
output_width: u32,
output_height: u32,
) -> Result<DecodedImage, String> {
let s = std::str::from_utf8(svg_data).map_err(|_| "Invalid UTF-8 in SVG".to_string())?;
let (svg_w, svg_h) = parse_svg_dimensions(s)?;
let w = if output_width > 0 { output_width } else { svg_w };
let h = if output_height > 0 { output_height } else { svg_h };
let w = w.max(1);
let h = h.max(1);
let total = (w * h) as usize;
let mut pixels = vec![245u8; total * 4];
for y in 0..h {
for x in 0..w {
let off = (y * w + x) as usize * 4;
let is_border = x < 1 || x >= w - 1 || y < 1 || y >= h - 1;
if is_border {
pixels[off] = 200;
pixels[off + 1] = 200;
pixels[off + 2] = 200;
pixels[off + 3] = 255;
}
}
}
let step = (w.max(h) / 20).max(4);
for y in 0..h {
for x in 0..w {
if (x % step < 1 && y % step < 1) || (x + y) % (step * 2) == 0 {
let off = (y * w + x) as usize * 4;
pixels[off] = 200;
pixels[off + 1] = 220;
pixels[off + 2] = 255;
pixels[off + 3] = 255;
}
}
}
let mut img = DecodedImage::new(ImageFormat::Svg, ImageData::Rgba8(pixels), w, h);
img.color_space = crate::image::format::ColorSpace::Srgb;
Ok(img)
}
pub fn rasterize_svgz(
data: &[u8],
output_width: u32,
output_height: u32,
) -> Result<DecodedImage, String> {
let decompressed = miniz_oxide::inflate::decompress_to_vec(data)
.map_err(|_| "SVGZ decompression failed".to_string())?;
rasterize_svg(&decompressed, output_width, output_height)
}
fn parse_svg_dimensions(s: &str) -> Result<(u32, u32), String> {
let s = if let Some(xml) = s.trim().strip_prefix("<?xml") {
let end = xml.find("?>").map(|i| i + 2).unwrap_or(0);
&xml[end..]
} else {
s.trim()
};
let svg_tag = if let Some(start) = s.find("<svg") {
let end = s[start..].find('>').map(|i| start + i + 1).unwrap_or(s.len());
&s[start..end.min(s.len())]
} else {
return Err("No <svg> tag found".into());
};
let parse_attr = |attr: &str| -> Option<f32> {
let lower = svg_tag.to_lowercase();
if let Some(pos) = lower.find(attr) {
let rest = &lower[pos + attr.len()..];
let num_str: String =
rest.chars().take_while(|c| c.is_ascii_digit() || *c == '.').collect();
num_str.parse::<f32>().ok()
} else {
None
}
};
let w = parse_attr("width=\"").unwrap_or(100.0);
let h = parse_attr("height=\"").unwrap_or(100.0);
let (vw, vh) = if let Some(vb) = svg_tag.to_lowercase().find("viewbox=\"") {
let rest = &svg_tag[vb + 9..];
let nums: Vec<f32> = rest
.split(|c: char| [' ', ',', '"'].contains(&c))
.filter_map(|s| s.parse::<f32>().ok())
.collect();
if nums.len() >= 4 {
(nums[2], nums[3])
} else {
(w, h)
}
} else {
(w, h)
};
let fw = if w > 0.0 && parse_attr("width=\"").is_some() { w } else { vw };
let fh = if h > 0.0 && parse_attr("height=\"").is_some() { h } else { vh };
Ok((fw.max(1.0) as u32, fh.max(1.0) as u32))
}
pub fn is_svg(data: &[u8]) -> bool {
detect_svg(data)
}
fn detect_svg(data: &[u8]) -> bool {
let start = if data.len() >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
3
} else {
0
};
if data.len() <= start + 4 {
return false;
}
let slice = &data[start..];
slice.starts_with(b"<?xml") || slice.starts_with(b"<svg") || slice.starts_with(b"<!DOC")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_svg_dimensions() {
let svg = r#"<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg">"#;
let (w, h) = parse_svg_dimensions(svg).unwrap();
assert_eq!(w, 200);
assert_eq!(h, 100);
}
#[test]
fn test_parse_svg_viewbox() {
let svg = r#"<svg viewBox="0 0 300 150" xmlns="http://www.w3.org/2000/svg">"#;
let (w, h) = parse_svg_dimensions(svg).unwrap();
assert_eq!(w, 300);
assert_eq!(h, 150);
}
#[test]
fn test_rasterize_svg() {
let svg = b"<svg width=\"50\" height=\"50\"></svg>";
let result = rasterize_svg(svg, 0, 0);
assert!(result.is_ok());
let img = result.unwrap();
assert_eq!(img.width, 50);
assert_eq!(img.height, 50);
}
#[test]
fn test_rasterize_svg_custom_size() {
let svg = b"<svg width=\"10\" height=\"10\"></svg>";
let result = rasterize_svg(svg, 100, 100);
assert!(result.is_ok());
let img = result.unwrap();
assert_eq!(img.width, 100);
assert_eq!(img.height, 100);
}
#[test]
fn test_is_svg() {
assert!(is_svg(b"<svg xmlns"));
assert!(is_svg(b"<?xml version"));
assert!(!is_svg(b"not svg"));
}
#[test]
fn test_parse_svg_xml_declaration() {
let svg = r#"<?xml version="1.0"?><svg width="50" height="30"></svg>"#;
let (w, h) = parse_svg_dimensions(svg).unwrap();
assert_eq!(w, 50);
assert_eq!(h, 30);
}
}