use crate::image_processor::Result;
use crate::vectorizer::{has_holes, Point, VectorizedData};
use std::fs::File;
use std::io::Write;
use std::path::Path;
pub fn generate_svg(vectorized_data: &VectorizedData, output_path: &Path) -> Result<()> {
let mut file = File::create(output_path)?;
let (bg_str, bg_opacity) = color_with_alpha(vectorized_data.background_color);
writeln!(
file,
r#"<svg width="{}" height="{}" viewBox="0 0 {} {}" xmlns="http://www.w3.org/2000/svg">"#,
vectorized_data.width,
vectorized_data.height,
vectorized_data.width,
vectorized_data.height
)?;
writeln!(
file,
r#" <rect width="{}" height="{}" fill="{}"{}/>"#,
vectorized_data.width, vectorized_data.height, bg_str, bg_opacity
)?;
for curve in &vectorized_data.curves {
let (color_str, opacity_attr) = color_with_alpha(curve.color);
let path_str = if !curve.subpaths.is_empty() {
create_multi_path_string(&curve.subpaths)
} else if !curve.points.is_empty() {
create_subpath_string(&curve.points, curve.is_closed)
} else {
continue;
};
if path_str.is_empty() {
continue;
}
let fill_rule = if has_holes(&curve.subpaths) {
r#" fill-rule="evenodd""#
} else {
""
};
writeln!(
file,
r#" <path d="{}" fill="{}"{}{} stroke="none"/>"#,
path_str, color_str, opacity_attr, fill_rule
)?;
}
writeln!(file, "</svg>")?;
Ok(())
}
pub fn generate_svg_advanced(vectorized_data: &VectorizedData, output_path: &Path) -> Result<()> {
generate_svg(vectorized_data, output_path)
}
pub fn create_multi_path_string(subpaths: &[Vec<Point>]) -> String {
let mut path = String::new();
for sp in subpaths {
if sp.len() < 3 {
continue;
}
if !path.is_empty() {
path.push(' ');
}
path.push_str(&create_subpath_string(sp, true));
}
path
}
pub fn color_with_alpha(color: (u8, u8, u8, u8)) -> (String, String) {
let hex = format!("#{:02x}{:02x}{:02x}", color.0, color.1, color.2);
if color.3 < 255 {
let opacity = (color.3 as f64 / 255.0 * 100.0).round() / 100.0;
(hex, format!(" fill-opacity=\"{}\"", opacity))
} else {
(hex, String::new())
}
}
fn fmt_coord(v: f64) -> String {
let rounded = (v * 2.0).round() / 2.0; if (rounded - rounded.round()).abs() < 0.01 {
format!("{}", rounded.round() as i32)
} else {
format!("{:.1}", rounded)
}
}
pub fn create_subpath_string(pts: &[Point], closed: bool) -> String {
let n = pts.len();
if n == 0 {
return String::new();
}
let mut path = format!("M{} {}", fmt_coord(pts[0].x), fmt_coord(pts[0].y));
for i in 1..n {
path.push_str(&format!("L{} {}", fmt_coord(pts[i].x), fmt_coord(pts[i].y)));
}
if closed {
path.push('Z');
}
path
}
#[cfg(test)]
mod tests {
include!("svg_generator_tests.rs");
}