From b52e0535317ca856728adebc67dd0cbee323baf0 Mon Sep 17 00:00:00 2001 From: Bryan Woods Date: Sat, 7 Dec 2024 15:43:36 -0800 Subject: [PATCH] svg2path --- composable-views/Cargo.toml | 13 ++- composable-views/src/shapes/bin.rs | 135 +++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 composable-views/src/shapes/bin.rs diff --git a/composable-views/Cargo.toml b/composable-views/Cargo.toml index 5f2c6bb..e3f5116 100644 --- a/composable-views/Cargo.toml +++ b/composable-views/Cargo.toml @@ -12,9 +12,13 @@ default = [] composable.workspace = true rustybuzz = "0.20.1" lyon = "1.0.1" -usvg = "0.44.0" svg = "0.18.0" +clap = { version = "4.3", features = ["derive"], optional = true } +usvg = { version = "0.44.0", optional = true } +convert_case = { version = "0.6.0", optional = true } +log = "0.4.22" + [dependencies.ui_id] path = "src/ui_id" @@ -22,16 +26,15 @@ path = "src/ui_id" [dev-dependencies] futures.workspace = true insta.workspace = true - ntest_timeout.workspace = true divan.workspace = true - winit = "0.30.5" meshopt = "0.4.0" muda = "0.15.3" wgpu = "23.0.0" + # platforms [target.'cfg(target_os = "windows")'.dependencies.windows-sys] features = ["Win32_UI", "Win32_UI_WindowsAndMessaging"] @@ -39,3 +42,7 @@ version = "0.59.0" [target.'cfg(target_os = "macos")'.dependencies.cocoa] version = "0.26.0" +[[bin]] +name = "svg2path" +path = "src/shapes/bin.rs" +required-features = ["clap", "usvg", "convert_case"] diff --git a/composable-views/src/shapes/bin.rs b/composable-views/src/shapes/bin.rs new file mode 100644 index 0000000..5c5775e --- /dev/null +++ b/composable-views/src/shapes/bin.rs @@ -0,0 +1,135 @@ +use std::fmt::Display; +use std::io::{stdout, Read, StdoutLock, Write}; +use std::ops::Add; + +use clap::{ArgGroup, Parser}; +use convert_case::{Case, Casing}; +use usvg::tiny_skia_path::PathSegment; +use usvg::{Node, Tree}; + +#[derive(Parser)] +#[clap(about, author, version)] +#[command(group(ArgGroup::new("resize").args(["scale", "width", "height"])))] +struct Args { + /// path to the SVG (or SGZ) file + path: std::path::PathBuf, + + /// scale the resulting paths by this amount + #[arg(short, long)] + scale: Option, + + /// scale the resulting paths to match this width + #[arg(short, long)] + width: Option, + + /// scale the resulting paths to match this height + #[arg(long)] + height: Option, +} + +fn main() -> Result<(), String> { + let args = Args::parse(); + + let mut data = Vec::new(); + let mut file = std::fs::File::open(&args.path).map_err(|err| err_msg(err, &args.path))?; + file.read_to_end(&mut data) + .map_err(|err| err_msg(err, &args.path))?; + + let name = args + .path + .as_path() + .file_stem() + .ok_or(err_msg("no filename?", &args.path))? + .to_string_lossy() + .to_case(Case::Snake); + + let resize = match (args.scale, args.width, args.height) { + (Some(s), None, None) => Resize::Scale(s), + (None, Some(w), None) => Resize::Width(w), + (None, None, Some(h)) => Resize::Height(h), + _ => Resize::Scale(1.0), + }; + + let options = usvg::Options::default(); + let svg = Tree::from_data(&data, &options).map_err(|err| err_msg(err, &args.path))?; + svg_paths(&name, resize, svg).map_err(|err| err_msg(err, &args.path)) +} + +enum Resize { + Scale(f32), + Width(f32), + Height(f32), +} + +#[rustfmt::skip] +fn svg_paths(name: &str, resize: Resize, svg: usvg::Tree) -> std::io::Result<()> { + let size = svg.size(); + + let scale = match resize { + Resize::Scale(scale) => scale, + Resize::Width(width) => width / size.width(), + Resize::Height(height) => height / size.height(), + }; + + let mut lock = stdout().lock(); + writeln!(lock, "fn {name}(x: f32, y: f32, w: f32, h: f32, rgba: [u8; 4], transform: &Transform, output: &mut impl Output) {{")?; + writeln!(lock, " let transform = transform")?; + writeln!(lock, " .pre_translate((x, y).into())")?; + writeln!(lock, " .pre_scale(w / {:?}, h / {:?});",size.width() * scale,size.width() * scale)?; + + recurse(scale, &mut lock, svg.root().children())?; + writeln!(lock, "}}") +} + +#[rustfmt::skip] +fn recurse(scale: f32, lock: &mut StdoutLock, nodes: &[Node]) -> std::io::Result<()> { + for element in nodes { + let mut transform = element.abs_transform(); + transform = transform.post_scale(scale, scale); + + match element { + Node::Group(group) => recurse(scale, lock, group.children())?, + Node::Path(path) => { + for segment in path.data().segments() { + match segment { + PathSegment::MoveTo(mut p) => { + transform.map_point(&mut p); + writeln!(lock)?; + writeln!(lock, " output.begin({:?}, {:?}, rgba, &transform);", p.x, p.y)?; + } + PathSegment::LineTo(mut p) => { + transform.map_point(&mut p); + writeln!(lock, " output.line_to({:?}, {:?});", p.x, p.y)?; + } + PathSegment::QuadTo(mut p1, mut p) => { + transform.map_point(&mut p); + transform.map_point(&mut p1); + writeln!(lock, " output.quadratic_bezier_to({:?}, {:?}, {:?}, {:?});",p1.x, p1.y, p.x, p.y)?; + } + PathSegment::CubicTo(mut p1, mut p2, mut p) => { + transform.map_point(&mut p); + transform.map_point(&mut p1); + transform.map_point(&mut p2); + writeln!(lock, " output.cubic_bezier_to({:?}, {:?}, {:?}, {:?}, {:?}, {:?});",p1.x, p1.y, p2.x, p2.y, p.x, p.y)?; + } + PathSegment::Close => { + writeln!(lock, " output.close();")?; + } + } + } + } + _ => {} + } + } + + Ok(()) +} + +fn err_msg(error: E, path: &std::path::Path) -> String { + path.file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned() + .add(": ") + .add(&error.to_string()) +}