1use crate::{
6 embedded_assets::{EmbeddedAssetsError, EmbeddedAssetsResult},
7 Cached,
8};
9use proc_macro2::TokenStream;
10use quote::{quote, ToTokens, TokenStreamExt};
11use std::{ffi::OsStr, io::Cursor, path::Path};
12
13pub(crate) enum IconFormat {
15 Raw,
17
18 Image { width: u32, height: u32 },
20}
21
22pub struct CachedIcon {
23 cache: Cached,
24 format: IconFormat,
25 root: TokenStream,
26}
27
28impl CachedIcon {
29 pub fn new(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
30 match icon.extension().map(OsStr::to_string_lossy).as_deref() {
31 Some("png") => Self::new_png(root, icon),
32 Some("ico") => Self::new_ico(root, icon),
33 unknown => Err(EmbeddedAssetsError::InvalidImageExtension {
34 extension: unknown.unwrap_or_default().into(),
35 path: icon.to_path_buf(),
36 }),
37 }
38 }
39
40 pub fn new_raw(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
42 let buf = Self::open(icon);
43 Cached::try_from(buf).map(|cache| Self {
44 cache,
45 root: root.clone(),
46 format: IconFormat::Raw,
47 })
48 }
49
50 pub fn new_ico(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
52 let buf = Self::open(icon);
53
54 let icon_dir = ico::IconDir::read(Cursor::new(&buf))
55 .unwrap_or_else(|e| panic!("failed to parse icon {}: {}", icon.display(), e));
56
57 let entry = &icon_dir.entries()[0];
58 let rgba = entry
59 .decode()
60 .unwrap_or_else(|e| panic!("failed to decode icon {}: {}", icon.display(), e))
61 .rgba_data()
62 .to_vec();
63
64 Cached::try_from(rgba).map(|cache| Self {
65 cache,
66 root: root.clone(),
67 format: IconFormat::Image {
68 width: entry.width(),
69 height: entry.height(),
70 },
71 })
72 }
73
74 pub fn new_png(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
76 let buf = Self::open(icon);
77 let decoder = png::Decoder::new(Cursor::new(&buf));
78 let mut reader = decoder
79 .read_info()
80 .unwrap_or_else(|e| panic!("failed to read icon {}: {}", icon.display(), e));
81
82 if reader.output_color_type().0 != png::ColorType::Rgba {
83 panic!("icon {} is not RGBA", icon.display());
84 }
85
86 let mut rgba = Vec::with_capacity(reader.output_buffer_size());
87 while let Ok(Some(row)) = reader.next_row() {
88 rgba.extend(row.data());
89 }
90
91 Cached::try_from(rgba).map(|cache| Self {
92 cache,
93 root: root.clone(),
94 format: IconFormat::Image {
95 width: reader.info().width,
96 height: reader.info().height,
97 },
98 })
99 }
100
101 fn open(path: &Path) -> Vec<u8> {
102 std::fs::read(path).unwrap_or_else(|e| panic!("failed to open icon {}: {}", path.display(), e))
103 }
104}
105
106impl ToTokens for CachedIcon {
107 fn to_tokens(&self, tokens: &mut TokenStream) {
108 let root = &self.root;
109 let cache = &self.cache;
110 let raw = quote!(::std::include_bytes!(#cache));
111 tokens.append_all(match self.format {
112 IconFormat::Raw => raw,
113 IconFormat::Image { width, height } => {
114 quote!(#root::image::Image::new(#raw, #width, #height))
115 }
116 })
117 }
118}