// this code is taken from https://github.com/MCorange99/svg2colored-png/tree/main // I was unable to figure out how to use usvg myself so yoinked it from here. use std::{ ffi::OsStr, path::{Path, PathBuf}, sync::Arc, }; use color_eyre::{eyre::bail, Result}; #[derive(Debug, Clone)] pub struct Args { pub input: PathBuf, /// Output folder where the PNG's will be placed pub output: PathBuf, /// Comma separated colors that will be used in HEX Eg. 000000,ffffff /// Can be like an object: black:000000,white:ffffff pub colors: String, /// Width of the generated PNG's pub width: u32, /// Height of the generated PNG's pub height: u32, } #[derive(Debug, Clone)] enum ColorType { Array(Vec), Object(Vec<(String, String)>), None, } #[derive(Debug, Clone)] pub struct Renderer { fontdb: Arc, colors: ColorType, size: (u32, u32), pub count: u64, } impl Renderer { pub fn new(args: &Args) -> Result { let mut db = usvg::fontdb::Database::default(); db.load_system_fonts(); let mut this = Self { fontdb: Arc::new(db), colors: ColorType::None, size: (args.width, args.height), count: 0, }; this.colors = if args.colors.contains(':') { let obj = args.colors.split(',').map(|s| { let mut iter = s.split(':'); let [Some(a), Some(b), None] = std::array::from_fn(|_| iter.next()) else { dbg!("Invalid color object, try checking help"); return None; }; Some((a.to_string(), b.to_string())) }); let colors = obj .flatten() .map(|c| { std::fs::create_dir_all(args.output.join(&c.0))?; Ok(c) }) .collect::>()?; ColorType::Object(colors) } else { let colors = args .colors .split(',') .map(|color| -> std::io::Result { std::fs::create_dir_all(args.output.join(color))?; Ok(color.to_string()) }) .collect::>()?; ColorType::Array(colors) }; Ok(this) } pub fn render(&mut self, fi: &Path, args: &Args) -> Result<()> { match fi.extension() { Some(e) if e.to_str() == Some("svg") => {} Some(_) | None => { dbg!("Filer {:?} is not of type SVG", fi); // util::logger::warning(format!("File '{}' is not of SVG type", fi.clone().to_str().unwrap())); bail!("Failed to render"); } }; match self.colors.clone() { ColorType::Array(c) => { for color in c { // log::info!("Rendering the color {color:?}"); let fo = self.get_out_file(fi, &color, args); self.render_one(fi, &fo, &color)?; } } ColorType::Object(c) => { for o in c { // log::info!("Rendering the color {:?}", o); let fo = self.get_out_file(fi, &o.0, args); self.render_one(fi, &fo, &o.1)?; } } ColorType::None => unreachable!(), } Ok(()) } fn render_one(&mut self, fi: &Path, fo: &Path, color: &String) -> Result<()> { if fo.exists() { dbg!("File {fo:?} exists, skipping"); return Ok(()); } let svg = self.set_color(&self.get_svg_data(fi)?, color); let opt = usvg::Options { // Get file's absolute directory. resources_dir: std::fs::canonicalize(fi).ok().and_then(|p| p.parent().map(|p| p.to_path_buf())), fontdb: self.fontdb.clone(), ..Default::default() }; let tree = match usvg::Tree::from_data(svg.as_bytes(), &opt) { Ok(v) => v, Err(_) => { dbg!("Failed to parse {fi:?}"); bail!(""); } }; let mut pixmap = tiny_skia::Pixmap::new(self.size.0, self.size.1).unwrap(); let scale = { let x = tree.size().width() / self.size.0 as f32; let y = tree.size().height() / self.size.0 as f32; x.min(y) }; resvg::render(&tree, usvg::Transform::default().post_scale(scale, scale), &mut pixmap.as_mut()); pixmap.save_png(fo)?; self.count += 1; Ok(()) } #[inline] fn get_out_file(&mut self, fi: &Path, _sub_folder: &str, args: &Args) -> PathBuf { let mut fo: std::path::PathBuf = args.output.clone(); // fo.push(sub_folder); fo.push(fi.file_name().unwrap_or(OsStr::new("default")).to_str().unwrap_or("default").replace(".svg", "")); fo.set_extension("png"); fo } fn set_color(&self, svg: &str, color: &String) -> String { svg.replace("fill=\"currentColor\"", &format!("fill=\"#{color}\"")) } fn get_svg_data(&self, fi: &Path) -> Result { match std::fs::read_to_string(fi) { Ok(d) => Ok(d), Err(_) => { dbg!("File {fi:?} does not exist"); bail!("File {fi:?} does not exist"); } } } }