vello/examples/scenes/src/svg.rs
Bruce Mitchener a66e94f7b9 deps: Update usvg to 0.33 from 0.29
This is not the latest version currently available, but the
changes to update to 0.34 are more involved, so we'll do
this intermediate step now.
2023-07-18 10:58:37 +07:00

167 lines
5.9 KiB
Rust

use std::{
fs::read_dir,
path::{Path, PathBuf},
};
use anyhow::{Ok, Result};
use instant::Instant;
use vello::{kurbo::Vec2, SceneBuilder, SceneFragment};
use vello_svg::usvg;
use vello_svg::usvg::TreeParsing;
use crate::{ExampleScene, SceneParams, 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(extension) = Path::new(&entry.file_name()).extension() {
if extension == "svg" {
count += 1;
scenes.push(example_scene_of(entry.path()))
}
}
}
// Ensure a consistent order within directories
scenes[start_index..].sort_by_key(|scene| scene.config.name.to_lowercase());
if count == 0 {
empty_dir();
}
} else {
scenes.push(example_scene_of(path.to_owned()))
}
}
Ok(SceneSet { scenes })
}
fn example_scene_of(file: PathBuf) -> ExampleScene {
let name = file
.file_stem()
.map(|it| it.to_string_lossy().to_string())
.unwrap_or_else(|| "unknown".to_string());
ExampleScene {
function: Box::new(svg_function_of(name.clone(), move || {
std::fs::read_to_string(file).expect("failed to read svg file")
})),
config: crate::SceneConfig {
animated: false,
name,
},
}
}
pub fn svg_function_of<R: AsRef<str>>(
name: String,
contents: impl FnOnce() -> R + Send + 'static,
) -> impl FnMut(&mut SceneBuilder, &mut SceneParams) {
fn render_svg_contents(name: &str, contents: &str) -> (SceneFragment, Vec2) {
let start = Instant::now();
let svg = usvg::Tree::from_str(contents, &usvg::Options::default())
.expect("failed to parse svg file");
eprintln!("Parsed svg {name} in {:?}", start.elapsed());
let start = Instant::now();
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());
eprintln!("Encoded svg {name} in {:?}", start.elapsed());
(new_scene, resolution)
}
let mut cached_scene = None;
#[cfg(not(target_arch = "wasm32"))]
let (tx, rx) = std::sync::mpsc::channel();
#[cfg(not(target_arch = "wasm32"))]
let mut tx = Some(tx);
#[cfg(not(target_arch = "wasm32"))]
let mut has_started_parse = false;
let mut contents = Some(contents);
move |builder, params| {
if let Some((scene_frag, resolution)) = cached_scene.as_mut() {
builder.append(scene_frag, None);
params.resolution = Some(*resolution);
return;
}
if cfg!(target_arch = "wasm32") || !params.interactive {
let contents = contents.take().unwrap();
let contents = contents();
let (scene_frag, resolution) = render_svg_contents(&name, contents.as_ref());
builder.append(&scene_frag, None);
params.resolution = Some(resolution);
cached_scene = Some((scene_frag, resolution));
return;
}
#[cfg(not(target_arch = "wasm32"))]
{
let mut timeout = std::time::Duration::from_millis(10);
if !has_started_parse {
has_started_parse = true;
// Prefer jank over loading screen for first time
timeout = std::time::Duration::from_millis(75);
let tx = tx.take().unwrap();
let contents = contents.take().unwrap();
let name = name.clone();
std::thread::spawn(move || {
let contents = contents();
tx.send(render_svg_contents(&name, contents.as_ref()))
.unwrap();
});
}
let recv = rx.recv_timeout(timeout);
use std::sync::mpsc::RecvTimeoutError;
match recv {
Result::Ok((scene_frag, resolution)) => {
builder.append(&scene_frag, None);
params.resolution = Some(resolution);
cached_scene = Some((scene_frag, resolution))
}
Err(RecvTimeoutError::Timeout) => params.text.add(
builder,
None,
48.,
None,
vello::kurbo::Affine::translate((110.0, 600.0)),
&format!("Loading {name}"),
),
Err(RecvTimeoutError::Disconnected) => {
panic!()
}
}
};
}
}