Skip to content

Commit 5b76994

Browse files
feat(core): add include_image macro (#9959)
1 parent 586a816 commit 5b76994

File tree

11 files changed

+251
-143
lines changed

11 files changed

+251
-143
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-codegen": "patch:feat"
3+
---
4+
5+
Add `include_image_codegen` function to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons.

.changes/include-image-macro.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": "patch:feat"
3+
"tauri-utils": "patch:feat"
4+
"tauri-macros": "patch:feat"
5+
---
6+
7+
Add `include_image` macro to help embedding instances of `Image` struct at compile-time in rust to be used with window, menu or tray icons.

core/tauri-codegen/src/context.rs

Lines changed: 19 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ use tauri_utils::platform::Target;
2525
use tauri_utils::plugin::GLOBAL_API_SCRIPT_FILE_LIST_PATH;
2626
use tauri_utils::tokens::{map_lit, str_lit};
2727

28-
use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError};
28+
use crate::embedded_assets::{
29+
ensure_out_dir, AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsResult,
30+
};
31+
use crate::image::{ico_icon, image_icon, png_icon, raw_icon};
2932

3033
const ACL_MANIFESTS_FILE_NAME: &str = "acl-manifests.json";
3134
const CAPABILITIES_FILE_NAME: &str = "capabilities.json";
@@ -65,7 +68,7 @@ fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut Csp
6568

6669
fn map_core_assets(
6770
options: &AssetOptions,
68-
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> Result<(), EmbeddedAssetsError> {
71+
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> EmbeddedAssetsResult<()> {
6972
let csp = options.csp;
7073
let dangerous_disable_asset_csp_modification =
7174
options.dangerous_disable_asset_csp_modification.clone();
@@ -92,7 +95,7 @@ fn map_core_assets(
9295
fn map_isolation(
9396
_options: &AssetOptions,
9497
dir: PathBuf,
95-
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> Result<(), EmbeddedAssetsError> {
98+
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> EmbeddedAssetsResult<()> {
9699
// create the csp for the isolation iframe styling now, to make the runtime less complex
97100
let mut hasher = Sha256::new();
98101
hasher.update(tauri_utils::pattern::isolation::IFRAME_STYLE);
@@ -129,7 +132,7 @@ fn map_isolation(
129132
}
130133

131134
/// Build a `tauri::Context` for including in application code.
132-
pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsError> {
135+
pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
133136
let ContextData {
134137
dev,
135138
config,
@@ -201,17 +204,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
201204
quote!(#assets)
202205
};
203206

204-
let out_dir = {
205-
let out_dir = std::env::var("OUT_DIR")
206-
.map_err(|_| EmbeddedAssetsError::OutDir)
207-
.map(PathBuf::from)
208-
.and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))?;
209-
210-
// make sure that our output directory is created
211-
std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
212-
213-
out_dir
214-
};
207+
let out_dir = ensure_out_dir()?;
215208

216209
let default_window_icon = {
217210
if target == Target::Windows {
@@ -223,15 +216,15 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
223216
"icons/icon.ico",
224217
);
225218
if icon_path.exists() {
226-
ico_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
219+
ico_icon(&root, &out_dir, &icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
227220
} else {
228221
let icon_path = find_icon(
229222
&config,
230223
&config_parent,
231224
|i| i.ends_with(".png"),
232225
"icons/icon.png",
233226
);
234-
png_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
227+
png_icon(&root, &out_dir, &icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
235228
}
236229
} else {
237230
// handle default window icons for Unix targets
@@ -241,7 +234,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
241234
|i| i.ends_with(".png"),
242235
"icons/icon.png",
243236
);
244-
png_icon(&root, &out_dir, icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
237+
png_icon(&root, &out_dir, &icon_path).map(|i| quote!(::std::option::Option::Some(#i)))?
245238
}
246239
};
247240

@@ -260,7 +253,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
260253
"icons/icon.png",
261254
);
262255
}
263-
raw_icon(&out_dir, icon_path)?
256+
raw_icon(&out_dir, &icon_path)?
264257
} else {
265258
quote!(::std::option::Option::None)
266259
};
@@ -289,18 +282,8 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
289282
let with_tray_icon_code = if target.is_desktop() {
290283
if let Some(tray) = &config.app.tray_icon {
291284
let tray_icon_icon_path = config_parent.join(&tray.icon_path);
292-
let ext = tray_icon_icon_path.extension();
293-
if ext.map_or(false, |e| e == "ico") {
294-
ico_icon(&root, &out_dir, tray_icon_icon_path)
295-
.map(|i| quote!(context.set_tray_icon(Some(#i));))?
296-
} else if ext.map_or(false, |e| e == "png") {
297-
png_icon(&root, &out_dir, tray_icon_icon_path)
298-
.map(|i| quote!(context.set_tray_icon(Some(#i));))?
299-
} else {
300-
quote!(compile_error!(
301-
"The tray icon extension must be either `.ico` or `.png`."
302-
))
303-
}
285+
image_icon(&root, &out_dir, &tray_icon_icon_path)
286+
.map(|i| quote!(context.set_tray_icon(Some(#i));))?
304287
} else {
305288
quote!()
306289
}
@@ -504,122 +487,18 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
504487
}))
505488
}
506489

507-
fn ico_icon<P: AsRef<Path>>(
508-
root: &TokenStream,
509-
out_dir: &Path,
510-
path: P,
511-
) -> Result<TokenStream, EmbeddedAssetsError> {
512-
let path = path.as_ref();
513-
let bytes = std::fs::read(path)
514-
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
515-
.to_vec();
516-
let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes))
517-
.unwrap_or_else(|e| panic!("failed to parse icon {}: {}", path.display(), e));
518-
let entry = &icon_dir.entries()[0];
519-
let rgba = entry
520-
.decode()
521-
.unwrap_or_else(|e| panic!("failed to decode icon {}: {}", path.display(), e))
522-
.rgba_data()
523-
.to_vec();
524-
let width = entry.width();
525-
let height = entry.height();
526-
527-
let icon_file_name = path.file_name().unwrap();
528-
let out_path = out_dir.join(icon_file_name);
529-
write_if_changed(&out_path, &rgba).map_err(|error| EmbeddedAssetsError::AssetWrite {
530-
path: path.to_owned(),
531-
error,
532-
})?;
533-
534-
let icon_file_name = icon_file_name.to_str().unwrap();
535-
let icon = quote!(#root::image::Image::new(include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_file_name)), #width, #height));
536-
Ok(icon)
537-
}
538-
539-
fn raw_icon<P: AsRef<Path>>(out_dir: &Path, path: P) -> Result<TokenStream, EmbeddedAssetsError> {
540-
let path = path.as_ref();
541-
let bytes = std::fs::read(path)
542-
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
543-
.to_vec();
544-
545-
let out_path = out_dir.join(path.file_name().unwrap());
546-
write_if_changed(&out_path, &bytes).map_err(|error| EmbeddedAssetsError::AssetWrite {
547-
path: path.to_owned(),
548-
error,
549-
})?;
550-
551-
let icon_path = path.file_name().unwrap().to_str().unwrap().to_string();
552-
let icon = quote!(::std::option::Option::Some(
553-
include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_path)).to_vec()
554-
));
555-
Ok(icon)
556-
}
557-
558-
fn png_icon<P: AsRef<Path>>(
559-
root: &TokenStream,
560-
out_dir: &Path,
561-
path: P,
562-
) -> Result<TokenStream, EmbeddedAssetsError> {
563-
let path = path.as_ref();
564-
let bytes = std::fs::read(path)
565-
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
566-
.to_vec();
567-
let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
568-
let mut reader = decoder
569-
.read_info()
570-
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e));
571-
572-
let (color_type, _) = reader.output_color_type();
573-
574-
if color_type != png::ColorType::Rgba {
575-
panic!("icon {} is not RGBA", path.display());
576-
}
577-
578-
let mut buffer: Vec<u8> = Vec::new();
579-
while let Ok(Some(row)) = reader.next_row() {
580-
buffer.extend(row.data());
581-
}
582-
let width = reader.info().width;
583-
let height = reader.info().height;
584-
585-
let icon_file_name = path.file_name().unwrap();
586-
let out_path = out_dir.join(icon_file_name);
587-
write_if_changed(&out_path, &buffer).map_err(|error| EmbeddedAssetsError::AssetWrite {
588-
path: path.to_owned(),
589-
error,
590-
})?;
591-
592-
let icon_file_name = icon_file_name.to_str().unwrap();
593-
let icon = quote!(#root::image::Image::new(include_bytes!(concat!(std::env!("OUT_DIR"), "/", #icon_file_name)), #width, #height));
594-
Ok(icon)
595-
}
596-
597-
fn write_if_changed(out_path: &Path, data: &[u8]) -> std::io::Result<()> {
598-
use std::fs::File;
599-
use std::io::Write;
600-
601-
if let Ok(curr) = std::fs::read(out_path) {
602-
if curr == data {
603-
return Ok(());
604-
}
605-
}
606-
607-
let mut out_file = File::create(out_path)?;
608-
out_file.write_all(data)
609-
}
610-
611-
fn find_icon<F: Fn(&&String) -> bool>(
490+
fn find_icon(
612491
config: &Config,
613492
config_parent: &Path,
614-
predicate: F,
493+
predicate: impl Fn(&&String) -> bool,
615494
default: &str,
616495
) -> PathBuf {
617496
let icon_path = config
618497
.bundle
619498
.icon
620499
.iter()
621-
.find(|i| predicate(i))
622-
.cloned()
623-
.unwrap_or_else(|| default.to_string());
500+
.find(predicate)
501+
.map(AsRef::as_ref)
502+
.unwrap_or(default);
624503
config_parent.join(icon_path)
625504
}

core/tauri-codegen/src/embedded_assets.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ pub enum EmbeddedAssetsError {
4848
#[error("invalid prefix {prefix} used while including path {path}")]
4949
PrefixInvalid { prefix: PathBuf, path: PathBuf },
5050

51+
#[error("invalid extension {extension} used for image {path}, must be `ico` or `png`")]
52+
InvalidImageExtension { extension: PathBuf, path: PathBuf },
53+
5154
#[error("failed to walk directory {path} because {error}")]
5255
Walkdir {
5356
path: PathBuf,
@@ -61,6 +64,8 @@ pub enum EmbeddedAssetsError {
6164
Version(#[from] semver::Error),
6265
}
6366

67+
pub type EmbeddedAssetsResult<T> = Result<T, EmbeddedAssetsError>;
68+
6469
/// Represent a directory of assets that are compressed and embedded.
6570
///
6671
/// This is the compile time generation of [`tauri_utils::assets::Assets`] from a directory. Assets
@@ -439,3 +444,14 @@ impl ToTokens for EmbeddedAssets {
439444
}});
440445
}
441446
}
447+
448+
pub(crate) fn ensure_out_dir() -> EmbeddedAssetsResult<PathBuf> {
449+
let out_dir = std::env::var("OUT_DIR")
450+
.map_err(|_| EmbeddedAssetsError::OutDir)
451+
.map(PathBuf::from)
452+
.and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))?;
453+
454+
// make sure that our output directory is created
455+
std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
456+
Ok(out_dir)
457+
}

0 commit comments

Comments
 (0)