1use png;
4use std::path::{Path, PathBuf};
5use std::fs::File;
6use std::env;
7use std::io;
8use std::fmt;
9use std::fmt::{Display, Formatter};
10use std::io::Write;
11
12pub struct EmittedImage {
13 ident: String,
14 src: PathBuf,
15 width: u32,
16 height: u32,
17}
18
19pub enum ErrorKind {
20 IOErrorReading(PathBuf, io::Error),
21 IOErrorWriting(PathBuf, io::Error),
22 DecodingError(PathBuf, png::DecodingError),
23 InvalidIdent,
24}
25pub struct Error {
26 err: ErrorKind,
27 image_ident: String,
28}
29
30impl Display for Error {
31 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
32 f.write_str("buildtime-png: ")?;
33 match &self.err {
34 ErrorKind::IOErrorReading(path, e) => write!(f, "error reading {}: {}", path.to_string_lossy(), &e),
35 ErrorKind::IOErrorWriting(path, e) => write!(f, "error writing {}: {}", path.to_string_lossy(), &e),
36 ErrorKind::DecodingError(path, e) => write!(f, "error decoding {}: {}", path.to_string_lossy(), &e),
37 ErrorKind::InvalidIdent => write!(f, "identifier {:?} is not a valid rust identifier.", &self.image_ident),
38 }
39 }
40}
41
42impl EmittedImage {
43 fn is_valid_ident(ident: &str) -> bool {
44 if ident.len() == 0 {
45 return false;
46 }
47 let allowed_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
48 for c in ident.chars() {
49 if allowed_chars.find(c).is_none() {
50 return false;
51 }
52 }
53 let digits = "0123456789";
54 if digits.find(ident.chars().next().unwrap()).is_some() {
55 return false;
56 }
57 true
58 }
59
60 fn emit<P1: AsRef<Path>, P2: AsRef<Path>>(root: P1, outdir: P1, path_rel_to_root: P2, ident: &str) -> Result<Self, Error> {
61 let ident = String::from(ident);
62 if !EmittedImage::is_valid_ident(&ident) {
63 return Err(Error{err: ErrorKind::InvalidIdent, image_ident: ident});
64 }
65 let inpath = root.as_ref().join(path_rel_to_root);
66 let f = match File::open(&inpath) {
67 Ok(f) => f,
68 Err(e) => return Err(Error{err: ErrorKind::IOErrorReading(inpath, e), image_ident: ident}),
69 };
70 let (info, mut reader) = match png::Decoder::new(f).read_info() {
71 Ok(k) => k,
72 Err(e) => return Err(Error{err: ErrorKind::DecodingError(inpath, e), image_ident: ident}),
73 };
74 let outpath = outdir.as_ref().join(format!("buildtime-png-{}.data", ident));
75 let mut outf = match std::fs::OpenOptions::new().create(true).truncate(true).write(true).open(&outpath) {
76 Ok(f) => f,
77 Err(e) => return Err(Error{err: ErrorKind::IOErrorWriting(outpath, e), image_ident: ident}),
78 };
79 let mut frame = vec![0u8; info.buffer_size()];
80 match reader.next_frame(frame.as_mut_slice()) {
81 Ok(()) => {},
82 Err(e) => return Err(Error{err: ErrorKind::DecodingError(inpath, e), image_ident: ident}),
83 };
84 match outf.write_all(frame.as_slice()) {
85 Ok(()) => {},
86 Err(e) => return Err(Error{err: ErrorKind::IOErrorWriting(outpath, e), image_ident: ident}),
87 };
88 Ok(Self{ident, src: outpath, width: info.width, height: info.height})
89 }
90
91 pub fn to_source(&self) -> String {
95 format!(r#"Image{{data: include_bytes!({src:?}), width: {width:?}, height: {height:?}}}"#,
96 src = self.src.canonicalize().unwrap(),
97 width = self.width,
98 height = self.height)
99 }
100}
101
102#[test]
103fn is_valid_ident_test() {
104 assert_eq!(EmittedImage::is_valid_ident("aaaa"), true);
105 assert_eq!(EmittedImage::is_valid_ident("1234"), false);
106 assert_eq!(EmittedImage::is_valid_ident("1aaa"), false);
107 assert_eq!(EmittedImage::is_valid_ident("aaa1"), true);
108 assert_eq!(EmittedImage::is_valid_ident("a1"), true);
109 assert_eq!(EmittedImage::is_valid_ident("1a"), false);
110 assert_eq!(EmittedImage::is_valid_ident("1"), false);
111 assert_eq!(EmittedImage::is_valid_ident(""), false);
112 assert_eq!(EmittedImage::is_valid_ident("aaaa/aaa"), false);
113 assert_eq!(EmittedImage::is_valid_ident("aaaa::aaa"), false);
114 assert_eq!(EmittedImage::is_valid_ident("aaaa\naaa"), false);
115 assert_eq!(EmittedImage::is_valid_ident("aaa bbb"), false);
116 assert_eq!(EmittedImage::is_valid_ident(" bbb"), false);
117 assert_eq!(EmittedImage::is_valid_ident("bbb "), false);
118 assert_eq!(EmittedImage::is_valid_ident("r#aaa"), false);
119}
120
121pub struct Builder {
122 images: Vec<EmittedImage>,
123 root: PathBuf,
124 outdir: PathBuf,
125}
126
127impl Default for Builder {
128 fn default() -> Self {
129 Self{images: Vec::new(), root: PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()), outdir: PathBuf::from(env::var_os("OUT_DIR").unwrap())}
130 }
131}
132
133impl Builder {
134 pub fn new() -> Self {
136 Builder::default()
137 }
138
139 pub fn with_out_dir<P: AsRef<Path>>(&mut self, outdir: P) -> &mut Self {
146 self.outdir = PathBuf::from(outdir.as_ref());
147 self
148 }
149
150 pub fn include_png<P: AsRef<Path>>(&mut self, path_rel_to_root: P, ident: &str) -> &mut Self {
163 let em = match EmittedImage::emit(&self.root, &self.outdir, path_rel_to_root, ident) {
164 Ok(em) => em,
165 Err(e) => {
166 eprint!("{}\n\n", &e);
167 panic!(e);
168 },
169 };
170 self.images.push(em);
171 self
172 }
173
174 pub fn emit_source_file(&self) -> io::Result<()> {
176 let outpath = self.outdir.join("images.rs");
177 self.emit_source_file_at(outpath)
178 }
179
180 pub fn emit_source_file_at<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
185 let mut f = std::fs::OpenOptions::new().create(true).truncate(true).write(true).open(self.outdir.join(path))?;
186 f.write_all(b"\
187pub struct Image {
188 pub data: &'static [u8],
189 pub width: u32,
190 pub height: u32
191}
192
193")?;
194 for i in &self.images {
195 f.write_fmt(format_args!("pub static r#{}: Image = {};\n", &i.ident, &i.to_source()))?;
196 }
197 Ok(())
198 }
199}
200
201