Skip to content

Commit

Permalink
svg2path
Browse files Browse the repository at this point in the history
  • Loading branch information
bwoods committed Dec 7, 2024
1 parent 930dcce commit b52e053
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 3 deletions.
13 changes: 10 additions & 3 deletions composable-views/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,37 @@ 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"


[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"]
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"]
135 changes: 135 additions & 0 deletions composable-views/src/shapes/bin.rs
Original file line number Diff line number Diff line change
@@ -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<f32>,

/// scale the resulting paths to match this width
#[arg(short, long)]
width: Option<f32>,

/// scale the resulting paths to match this height
#[arg(long)]
height: Option<f32>,
}

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<E: Display>(error: E, path: &std::path::Path) -> String {
path.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned()
.add(": ")
.add(&error.to_string())
}

0 comments on commit b52e053

Please sign in to comment.