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 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}