viewBox fitting and resvg rendering

feat/template
Shad Amethyst 2 years ago
parent 3dd89016ab
commit 45c6895833
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -13,3 +13,7 @@ xmltree = "0.10.3"
mustache = { git = "https://git.shadamethyst.xyz/adri326/rust-mustache.git" }
clap = { version = "3.2", features = ["derive"] }
mkdirp = "1.0.0"
resvg = "0.23"
usvg = "0.23"
tiny-skia = "0.6"
png = "0.17"

@ -0,0 +1,128 @@
use usvg::{
Tree,
NodeExt,
Options,
};
use xmltree::{Element};
use std::path::{PathBuf};
use std::collections::HashSet;
#[derive(Debug)]
pub enum ExportError {
Xml(xmltree::Error),
XmlParse(xmltree::ParseError),
Usvg(usvg::Error),
Io(PathBuf, std::io::Error),
NoBBox,
Utf8(std::string::FromUtf8Error),
Encode(png::EncodingError),
}
impl From<xmltree::ParseError> for ExportError {
fn from(err: xmltree::ParseError) -> Self {
Self::XmlParse(err)
}
}
impl From<xmltree::Error> for ExportError {
fn from(err: xmltree::Error) -> Self {
Self::Xml(err)
}
}
impl From<usvg::Error> for ExportError {
fn from(err: usvg::Error) -> Self {
Self::Usvg(err)
}
}
impl From<std::string::FromUtf8Error> for ExportError {
fn from(err: std::string::FromUtf8Error) -> Self {
Self::Utf8(err)
}
}
impl From<png::EncodingError> for ExportError {
fn from(err: png::EncodingError) -> Self {
Self::Encode(err)
}
}
pub fn get_new_bbox(svg: &Tree) -> Option<(f64, f64, f64, f64)> {
let bbox = svg.root().calculate_bbox()?;
if bbox.width() > bbox.height() {
let y = bbox.y() - (bbox.width() - bbox.height()) / 2.0;
Some((bbox.x(), y, bbox.width(), bbox.width()))
} else {
let x = bbox.x() - (bbox.height() - bbox.width()) / 2.0;
Some((x, bbox.y(), bbox.height(), bbox.height()))
}
}
fn get_usvg(svg_str: &str) -> Result<usvg::Tree, usvg::Error> {
let usvg_options = Options::default();
Tree::from_str(svg_str, &usvg_options.to_ref())
}
fn get_xml(svg_str: &str) -> Result<Element, xmltree::ParseError> {
Element::parse(svg_str.as_bytes())
}
fn xml_to_str(svg_xml: &Element) -> Result<String, ExportError> {
let mut s: Vec<u8> = Vec::new();
svg_xml.write(&mut s)?;
Ok(String::from_utf8(s)?)
}
pub fn resize(svg_str: String) -> Result<String, ExportError> {
if let Some(new_bbox) = get_new_bbox(&get_usvg(&svg_str)?) {
let mut svg_xml = get_xml(&svg_str)?;
svg_xml.attributes.insert(
"viewBox".to_string(),
format!("{} {} {} {}", new_bbox.0, new_bbox.1, new_bbox.2, new_bbox.3),
);
xml_to_str(&svg_xml)
} else {
Err(ExportError::NoBBox)
}
}
pub fn export(
mut svg_str: String,
output_dir: &PathBuf,
output_name: String,
args: &super::Args,
) -> Result<(), ExportError> {
if args.resize {
svg_str = resize(svg_str)?;
}
mkdirp::mkdirp(output_dir.join("vector")).unwrap();
let output = output_dir.join(&format!("vector/{}.svg", output_name));
std::fs::write(output.clone(), svg_str.clone()).map_err(|err| ExportError::Io(output, err))?;
let svg_usvg = get_usvg(&svg_str)?;
for resolution in args.dim.iter().copied().filter(|r| *r != 0).collect::<HashSet<_>>() {
mkdirp::mkdirp(output_dir.join(&format!("{}", resolution))).unwrap();
let output = output_dir.join(&format!("{}/{}.png", resolution, output_name));
let mut image = tiny_skia::Pixmap::new(resolution, resolution).unwrap();
resvg::render(
&svg_usvg,
usvg::FitTo::Width(resolution),
tiny_skia::Transform::identity(),
image.as_mut()
).unwrap();
image.save_png(output)?;
}
Ok(())
}

@ -8,35 +8,45 @@ use parse::*;
pub mod template;
use template::*;
pub mod export;
use export::*;
fn main() {
let args = Args::parse();
let species = Arc::new(load_species(args.decl).unwrap());
let species = Arc::new(load_species(args.decl.clone()).unwrap());
let context = RenderingContext::new(species);
let output_dir = args.output_dir.unwrap_or(PathBuf::from("output/vector/"));
mkdirp::mkdirp(output_dir.clone()).unwrap();
let output_dir = args.output_dir.clone().unwrap_or(PathBuf::from("output/"));
if args.names.is_empty() {
for name in context.species().variants.keys() {
generate_variant(&context, name, &output_dir);
generate_variant(&context, name, &output_dir, &args);
}
} else {
for name in args.names.iter() {
generate_variant(&context, name, &output_dir);
generate_variant(&context, name, &output_dir, &args);
}
}
}
fn generate_variant(context: &RenderingContext, name: &str, output_dir: &PathBuf) {
fn generate_variant(context: &RenderingContext, name: &str, output_dir: &PathBuf, args: &Args) {
if let Some(path) = context.species().variants.get(name) {
match context.compile(path).and_then(|template| {
template.render_data_to_string(&context.get_data())
}) {
Ok(rendered) => {
let output = output_dir.join(&format!("{}_{}.svg", context.species().name, name));
std::fs::write(output, rendered).unwrap();
Ok(svg) => {
match export(
svg,
output_dir,
format!("{}_{}", context.species().name, name),
args
) {
Ok(_) => {}
Err(err) => {
eprintln!("Error while rendering {}: {:?}", name, err);
}
}
}
Err(err) => {
eprintln!("Error while rendering {}: {}", name, err);
@ -49,7 +59,7 @@ fn generate_variant(context: &RenderingContext, name: &str, output_dir: &PathBuf
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
pub struct Args {
/// A folder containing the declaration from which the emotes should be generated
#[clap(short, long, value_parser)]
decl: PathBuf,
@ -58,6 +68,14 @@ struct Args {
#[clap(value_parser)]
names: Vec<String>,
/// Automatically resize the SVG's viewBox, defaults to true
#[clap(short, long, value_parser, default_value = "true")]
resize: bool,
/// Dimension to export the images as; can be specified multiple times
#[clap(long, value_parser)]
dim: Vec<u32>,
/// Output directory
#[clap(short, long, value_parser)]
output_dir: Option<PathBuf>,

Loading…
Cancel
Save