svg2appicon/
lib.rs

1pub mod icon;
2
3use usvg::{Tree, SystemFontDB};
4use std::path::{Path, PathBuf};
5use std::fs::{self, File};
6use std::io::Write;
7use anyhow::{Context, Result, bail};
8use icon::Icon;
9use crate::icon::{IOS_ICONS, MAC_ICONS, WATCH_ICONS};
10use std::ffi::OsStr;
11
12pub enum SVG {
13    Data(Vec<u8>),
14    File(PathBuf),
15}
16
17pub struct Config {
18    pub assets_path: String,
19    pub svg_ios: Option<SVG>,
20    pub svg_mac: Option<SVG>,
21    pub svg_watch: Option<SVG>,
22}
23
24pub fn generate_icons(config: &Config) -> Result<()> {
25    //let a = Path::new(&config.assets_path).extension();
26
27    if Path::new(&config.assets_path).extension() != Some(OsStr::new("appiconset")) {
28        bail!("Please specify the path to .appiconset")
29    }
30
31    if Path::new(&config.assets_path).exists() {
32        fs::remove_dir_all(&config.assets_path)
33            .with_context(|| format!("Failed to remove {}", config.assets_path))?;
34    }
35    fs::create_dir(&config.assets_path)
36        .with_context(|| format!("Failed to create {}", config.assets_path))?;
37
38    let assets_path_str: &str = &config.assets_path;
39    let assets_path = Path::new(assets_path_str);
40
41    if let Some(svg) = &config.svg_ios {
42        generate_icon_files(assets_path, svg, &IOS_ICONS)?;
43    }
44
45    if let Some(svg) = &config.svg_mac {
46        generate_icon_files(assets_path, svg, &MAC_ICONS)?;
47    }
48
49    if let Some(svg) = &config.svg_watch {
50        generate_icon_files(assets_path, svg, &WATCH_ICONS)?;
51    }
52
53    let icons_set = get_icons_set(&config);
54    let json_str = get_json_str(&icons_set);
55
56    save_json(&assets_path, &json_str)?;
57
58    Ok(())
59}
60
61fn get_icons_set(config: &Config) -> Vec<&Vec<Icon>> {
62    let mut icons_set: Vec<&Vec<Icon>> = vec![];
63    if let Some(_) = config.svg_ios {
64        icons_set.push(&icon::IOS_ICONS);
65    }
66    if let Some(_) = config.svg_mac {
67        icons_set.push(&icon::MAC_ICONS);
68    }
69    if let Some(_) = config.svg_watch {
70        icons_set.push(&icon::WATCH_ICONS);
71    }
72
73    icons_set
74}
75
76fn get_json_str(icons_set: &Vec<&Vec<Icon>>) -> String {
77    let icons_json_str =
78        icons_set
79            .iter()
80            .map(|icons|
81                icons
82                    .iter()
83                    .map(|icon| icon.to_json())
84                    .collect::<Vec<String>>()
85                    .join(",\n")
86            )
87            .collect::<Vec<String>>()
88            .join(",\n");
89
90    format!(
91        "{{
92  \"images\" : [
93{}
94  ],
95  \"info\" : {{
96    \"author\" : \"xcode\",
97    \"version\" : 1
98  }}
99}}
100", icons_json_str)
101}
102
103fn save_json(assets_path: &Path, json_str: &String) -> Result<()> {
104    let path = assets_path.join("Contents.json");
105
106    let mut file = File::create(&path).with_context(|| "Failed to create Contents.json")?;
107
108    file.write_all(json_str.as_bytes()).with_context(|| "Failed to write to Contents.json")
109}
110
111fn get_tree(svg: &SVG) -> Result<Tree> {
112    match svg {
113        SVG::Data(data) =>
114            get_tree_from_data(data),
115        SVG::File(p) =>
116            get_tree_from_file(p)
117    }
118}
119
120fn get_tree_from_file<P: AsRef<Path>>(svg_path: P) -> Result<Tree> {
121    let mut opt = get_options();
122    opt.resources_dir = std::fs::canonicalize(&svg_path).ok().and_then(|p| p.parent().map(|p| p.to_path_buf()));
123
124    let svg_data = std::fs::read(&svg_path)?;
125
126    usvg::Tree::from_data(&svg_data, &opt)
127        .with_context(|| "Failed to process the svg file")
128}
129
130fn get_tree_from_data(svg_data: &Vec<u8>) -> Result<Tree> {
131    let opt = get_options();
132    usvg::Tree::from_data(svg_data, &opt)
133        .with_context(|| "Failed to process the svg file")
134}
135
136fn get_options() -> usvg::Options {
137    let mut opt = usvg::Options::default();
138    opt.fontdb.load_system_fonts();
139    opt.fontdb.set_generic_families();
140    opt
141}
142
143fn generate_icon_files(assets_path: &Path, svg: &SVG, icons: &Vec<Icon>) -> Result<()> {
144    let tree = get_tree(&svg)?;
145
146    for icon in icons {
147        let size = icon.size * (icon.scale as f64);
148        let size = size as u32;
149        let path = assets_path.join(&icon.get_filename());
150        let path: &str = path.to_str().with_context(|| "Failed to generate icon file path")?;
151        save_png(&tree, size, path)?;
152    }
153
154    Ok(())
155}
156
157fn save_png<P: AsRef<Path>>(tree: &Tree, size: u32, path: P) -> Result<()> {
158    let pixmap_size =
159        usvg::Size::new(size as f64, size as f64)
160            .with_context(|| "Failed to generate icon file")?
161            .to_screen_size();
162
163    let mut pixmap =
164        tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height())
165            .with_context(|| "Failed to genarate icon file")?;
166
167    resvg::render(&tree, usvg::FitTo::Size(size, size), pixmap.as_mut())
168        .with_context(|| "Failed to render svg image")?;
169
170    pixmap.save_png(&path)?;
171
172    Ok(())
173}