tauri_codegen/
image.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use 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
13/// The format the Icon is consumed as.
14pub(crate) enum IconFormat {
15  /// The image, completely unmodified.
16  Raw,
17
18  /// RGBA raw data, meant to be consumed by [`tauri::image::Image`].
19  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  /// Cache the icon without any manipulation.
41  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  /// Cache an ICO icon as RGBA data, see [`ImageFormat::Image`].
51  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  /// Cache a PNG icon as RGBA data, see [`ImageFormat::Image`].
75  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}