| use std::{ |
| fs::read_dir, |
| path::{Path, PathBuf}, |
| time::Instant, |
| }; |
| |
| use anyhow::{Ok, Result}; |
| use vello::{ |
| kurbo::{Affine, Vec2}, |
| SceneBuilder, SceneFragment, |
| }; |
| use vello_svg::usvg; |
| |
| use crate::{ExampleScene, SceneSet}; |
| |
| pub fn scene_from_files(files: &[PathBuf]) -> Result<SceneSet> { |
| scene_from_files_inner(files, || ()) |
| } |
| |
| pub fn default_scene(command: impl FnOnce() -> clap::Command) -> Result<SceneSet> { |
| let assets_dir = Path::new(env!("CARGO_MANIFEST_DIR")) |
| .join("../assets/") |
| .canonicalize()?; |
| let mut has_empty_directory = false; |
| let result = scene_from_files_inner( |
| &[ |
| assets_dir.join("Ghostscript_Tiger.svg"), |
| assets_dir.join("downloads"), |
| ], |
| || has_empty_directory = true, |
| )?; |
| if has_empty_directory { |
| let mut command = command(); |
| command.build(); |
| println!( |
| "No test files have been downloaded. Consider downloading some using the subcommand:" |
| ); |
| let subcmd = command.find_subcommand_mut("download").unwrap(); |
| subcmd.print_help()?; |
| } |
| Ok(result) |
| } |
| |
| fn scene_from_files_inner( |
| files: &[PathBuf], |
| mut empty_dir: impl FnMut(), |
| ) -> std::result::Result<SceneSet, anyhow::Error> { |
| let mut scenes = Vec::new(); |
| for path in files { |
| if path.is_dir() { |
| let mut count = 0; |
| let start_index = scenes.len(); |
| for file in read_dir(path)? { |
| let entry = file?; |
| if let Some(scene) = example_scene_of(entry.path()) { |
| count += 1; |
| scenes.push(scene); |
| } |
| } |
| // Ensure a consistent order within directories |
| scenes[start_index..].sort_by_key(|scene| scene.config.name.to_lowercase()); |
| if count == 0 { |
| empty_dir(); |
| } |
| } else { |
| if let Some(scene) = example_scene_of(path.to_owned()) { |
| scenes.push(scene); |
| } |
| } |
| } |
| Ok(SceneSet { scenes }) |
| } |
| |
| fn example_scene_of(file: PathBuf) -> Option<ExampleScene> { |
| let extension = file.extension()?; |
| if extension == "svg" { |
| Some(example_scene_of_svg(file)) |
| } else if extension == "json" { |
| Some(example_scene_of_lottie(file)) |
| } else { |
| None |
| } |
| } |
| |
| fn example_scene_of_svg(file: PathBuf) -> ExampleScene { |
| let name = file |
| .file_stem() |
| .map(|it| it.to_string_lossy().to_string()) |
| .unwrap_or_else(|| "unknown".to_string()); |
| let name_stored = name.clone(); |
| let mut cached_scene = None; |
| ExampleScene { |
| function: Box::new(move |builder, params| { |
| let (scene_frag, resolution) = cached_scene.get_or_insert_with(|| { |
| let start = Instant::now(); |
| let contents = std::fs::read_to_string(&file).expect("failed to read svg file"); |
| let svg = usvg::Tree::from_str(&contents, &usvg::Options::default()) |
| .expect("failed to parse svg file"); |
| eprintln!( |
| "Parsing SVG {name_stored} took {:?} (file `{file:?}`", |
| start.elapsed() |
| ); |
| let mut new_scene = SceneFragment::new(); |
| let mut builder = SceneBuilder::for_fragment(&mut new_scene); |
| vello_svg::render_tree(&mut builder, &svg); |
| let resolution = Vec2::new(svg.size.width(), svg.size.height()); |
| // TODO: Handle svg.view_box |
| (new_scene, resolution) |
| }); |
| builder.append(&scene_frag, None); |
| params.resolution = Some(*resolution); |
| }), |
| config: crate::SceneConfig { |
| animated: false, |
| name, |
| }, |
| } |
| } |
| |
| fn example_scene_of_lottie(file: PathBuf) -> ExampleScene { |
| let name = file |
| .file_stem() |
| .map(|it| it.to_string_lossy().to_string()) |
| .unwrap_or_else(|| "unknown".to_string()); |
| let name_stored = name.clone(); |
| let mut cached_scene = None; |
| ExampleScene { |
| function: Box::new(move |builder, params| { |
| let (composition, renderer, resolution) = cached_scene.get_or_insert_with(|| { |
| let start = Instant::now(); |
| let contents = std::fs::read(&file).expect("failed to read lottie file"); |
| let composition = velato::Composition::from_bytes(&contents) |
| .expect("failed to parse lottie file"); |
| eprintln!( |
| "Parsing Lottie {name_stored} took {:?} (file `{file:?}`", |
| start.elapsed() |
| ); |
| let resolution = Vec2::new(composition.width as f64, composition.height as f64); |
| (composition, velato::Renderer::new(), resolution) |
| }); |
| renderer.render( |
| composition, |
| params.time as f32, |
| Affine::IDENTITY, |
| 1.0, |
| builder, |
| ); |
| params.resolution = Some(*resolution); |
| }), |
| config: crate::SceneConfig { |
| animated: true, |
| name, |
| }, |
| } |
| } |