1use super::icons::{IconRole, IconSet};
8
9#[must_use]
36#[allow(unreachable_patterns, unused_variables)]
37pub(crate) fn bundled_icon_svg(role: IconRole, set: IconSet) -> Option<&'static [u8]> {
38 match set {
39 #[cfg(feature = "material-icons")]
40 IconSet::Material => material_svg(role),
41
42 #[cfg(feature = "lucide-icons")]
43 IconSet::Lucide => lucide_svg(role),
44
45 _ => None,
46 }
47}
48
49#[cfg(feature = "material-icons")]
50#[allow(unreachable_patterns)]
51fn material_svg(role: IconRole) -> Option<&'static [u8]> {
52 match role {
53 IconRole::DialogWarning => Some(include_bytes!("../../icons/material/warning.svg")),
55 IconRole::DialogError => Some(include_bytes!("../../icons/material/error.svg")),
56 IconRole::DialogInfo => Some(include_bytes!("../../icons/material/info.svg")),
57 IconRole::DialogQuestion => Some(include_bytes!("../../icons/material/help.svg")),
58 IconRole::DialogSuccess => Some(include_bytes!("../../icons/material/check_circle.svg")),
59 IconRole::Shield => Some(include_bytes!("../../icons/material/shield.svg")),
60
61 IconRole::WindowClose => Some(include_bytes!("../../icons/material/close.svg")),
63 IconRole::WindowMinimize => Some(include_bytes!("../../icons/material/minimize.svg")),
64 IconRole::WindowMaximize => Some(include_bytes!("../../icons/material/open_in_full.svg")),
65 IconRole::WindowRestore => {
66 Some(include_bytes!("../../icons/material/close_fullscreen.svg"))
67 }
68
69 IconRole::ActionSave => Some(include_bytes!("../../icons/material/save.svg")),
71 IconRole::ActionDelete => Some(include_bytes!("../../icons/material/delete.svg")),
72 IconRole::ActionCopy => Some(include_bytes!("../../icons/material/content_copy.svg")),
73 IconRole::ActionPaste => Some(include_bytes!("../../icons/material/content_paste.svg")),
74 IconRole::ActionCut => Some(include_bytes!("../../icons/material/content_cut.svg")),
75 IconRole::ActionUndo => Some(include_bytes!("../../icons/material/undo.svg")),
76 IconRole::ActionRedo => Some(include_bytes!("../../icons/material/redo.svg")),
77 IconRole::ActionSearch => Some(include_bytes!("../../icons/material/search.svg")),
78 IconRole::ActionSettings => Some(include_bytes!("../../icons/material/settings.svg")),
79 IconRole::ActionEdit => Some(include_bytes!("../../icons/material/edit.svg")),
80 IconRole::ActionAdd => Some(include_bytes!("../../icons/material/add.svg")),
81 IconRole::ActionRemove => Some(include_bytes!("../../icons/material/remove.svg")),
82 IconRole::ActionRefresh => Some(include_bytes!("../../icons/material/refresh.svg")),
83 IconRole::ActionPrint => Some(include_bytes!("../../icons/material/print.svg")),
84
85 IconRole::NavBack => Some(include_bytes!("../../icons/material/arrow_back.svg")),
87 IconRole::NavForward => Some(include_bytes!("../../icons/material/arrow_forward.svg")),
88 IconRole::NavUp => Some(include_bytes!("../../icons/material/arrow_upward.svg")),
89 IconRole::NavDown => Some(include_bytes!("../../icons/material/arrow_downward.svg")),
90 IconRole::NavHome => Some(include_bytes!("../../icons/material/home.svg")),
91 IconRole::NavMenu => Some(include_bytes!("../../icons/material/menu.svg")),
92
93 IconRole::FileGeneric => Some(include_bytes!("../../icons/material/description.svg")),
95 IconRole::FolderClosed => Some(include_bytes!("../../icons/material/folder.svg")),
96 IconRole::FolderOpen => Some(include_bytes!("../../icons/material/folder_open.svg")),
97 IconRole::TrashEmpty => Some(include_bytes!("../../icons/material/delete.svg")),
98 IconRole::TrashFull => Some(include_bytes!("../../icons/material/delete.svg")), IconRole::StatusBusy => Some(include_bytes!("../../icons/material/progress_activity.svg")),
102 IconRole::StatusCheck => Some(include_bytes!("../../icons/material/check.svg")),
103 IconRole::StatusError => Some(include_bytes!("../../icons/material/error.svg")), IconRole::UserAccount => Some(include_bytes!("../../icons/material/person.svg")),
107 IconRole::Notification => Some(include_bytes!("../../icons/material/notifications.svg")),
108 IconRole::Help => Some(include_bytes!("../../icons/material/help.svg")), IconRole::Lock => Some(include_bytes!("../../icons/material/lock.svg")),
110
111 _ => None, }
113}
114
115#[cfg(feature = "lucide-icons")]
116#[allow(unreachable_patterns)]
117fn lucide_svg(role: IconRole) -> Option<&'static [u8]> {
118 match role {
119 IconRole::DialogWarning => Some(include_bytes!("../../icons/lucide/triangle-alert.svg")),
121 IconRole::DialogError => Some(include_bytes!("../../icons/lucide/circle-x.svg")),
122 IconRole::DialogInfo => Some(include_bytes!("../../icons/lucide/info.svg")),
123 IconRole::DialogQuestion => Some(include_bytes!(
124 "../../icons/lucide/circle-question-mark.svg"
125 )),
126 IconRole::DialogSuccess => Some(include_bytes!("../../icons/lucide/circle-check.svg")),
127 IconRole::Shield => Some(include_bytes!("../../icons/lucide/shield.svg")),
128
129 IconRole::WindowClose => Some(include_bytes!("../../icons/lucide/x.svg")),
131 IconRole::WindowMinimize => Some(include_bytes!("../../icons/lucide/minimize.svg")),
132 IconRole::WindowMaximize => Some(include_bytes!("../../icons/lucide/maximize.svg")),
133 IconRole::WindowRestore => Some(include_bytes!("../../icons/lucide/minimize-2.svg")),
134
135 IconRole::ActionSave => Some(include_bytes!("../../icons/lucide/save.svg")),
137 IconRole::ActionDelete => Some(include_bytes!("../../icons/lucide/trash.svg")),
138 IconRole::ActionCopy => Some(include_bytes!("../../icons/lucide/copy.svg")),
139 IconRole::ActionPaste => Some(include_bytes!("../../icons/lucide/clipboard-paste.svg")),
140 IconRole::ActionCut => Some(include_bytes!("../../icons/lucide/scissors.svg")),
141 IconRole::ActionUndo => Some(include_bytes!("../../icons/lucide/undo-2.svg")),
142 IconRole::ActionRedo => Some(include_bytes!("../../icons/lucide/redo-2.svg")),
143 IconRole::ActionSearch => Some(include_bytes!("../../icons/lucide/search.svg")),
144 IconRole::ActionSettings => Some(include_bytes!("../../icons/lucide/settings.svg")),
145 IconRole::ActionEdit => Some(include_bytes!("../../icons/lucide/pencil.svg")),
146 IconRole::ActionAdd => Some(include_bytes!("../../icons/lucide/plus.svg")),
147 IconRole::ActionRemove => Some(include_bytes!("../../icons/lucide/minus.svg")),
148 IconRole::ActionRefresh => Some(include_bytes!("../../icons/lucide/refresh-cw.svg")),
149 IconRole::ActionPrint => Some(include_bytes!("../../icons/lucide/printer.svg")),
150
151 IconRole::NavBack => Some(include_bytes!("../../icons/lucide/chevron-left.svg")),
153 IconRole::NavForward => Some(include_bytes!("../../icons/lucide/chevron-right.svg")),
154 IconRole::NavUp => Some(include_bytes!("../../icons/lucide/chevron-up.svg")),
155 IconRole::NavDown => Some(include_bytes!("../../icons/lucide/chevron-down.svg")),
156 IconRole::NavHome => Some(include_bytes!("../../icons/lucide/house.svg")),
157 IconRole::NavMenu => Some(include_bytes!("../../icons/lucide/menu.svg")),
158
159 IconRole::FileGeneric => Some(include_bytes!("../../icons/lucide/file.svg")),
161 IconRole::FolderClosed => Some(include_bytes!("../../icons/lucide/folder-closed.svg")),
162 IconRole::FolderOpen => Some(include_bytes!("../../icons/lucide/folder-open.svg")),
163 IconRole::TrashEmpty => Some(include_bytes!("../../icons/lucide/trash.svg")),
164 IconRole::TrashFull => Some(include_bytes!("../../icons/lucide/trash.svg")), IconRole::StatusBusy => Some(include_bytes!("../../icons/lucide/loader.svg")),
168 IconRole::StatusCheck => Some(include_bytes!("../../icons/lucide/check.svg")),
169 IconRole::StatusError => Some(include_bytes!("../../icons/lucide/circle-x.svg")), IconRole::UserAccount => Some(include_bytes!("../../icons/lucide/user.svg")),
173 IconRole::Notification => Some(include_bytes!("../../icons/lucide/bell.svg")),
174 IconRole::Help => Some(include_bytes!(
175 "../../icons/lucide/circle-question-mark.svg"
176 )), IconRole::Lock => Some(include_bytes!("../../icons/lucide/lock.svg")),
178
179 _ => None, }
181}
182
183#[must_use]
205#[allow(unreachable_patterns, unused_variables)]
206#[cfg_attr(
207 not(any(feature = "material-icons", feature = "lucide-icons")),
208 allow(dead_code)
209)]
210pub(crate) fn bundled_icon_by_name(name: &str, set: IconSet) -> Option<&'static [u8]> {
211 match set {
212 #[cfg(feature = "material-icons")]
213 IconSet::Material => material_svg_by_name(name),
214
215 #[cfg(feature = "lucide-icons")]
216 IconSet::Lucide => lucide_svg_by_name(name),
217
218 _ => None,
219 }
220}
221
222#[cfg(feature = "lucide-icons")]
223include!(concat!(env!("OUT_DIR"), "/lucide_svg_by_name.rs"));
224
225#[cfg(feature = "material-icons")]
226include!(concat!(env!("OUT_DIR"), "/material_svg_by_name.rs"));
227
228#[cfg(test)]
229#[allow(clippy::unwrap_used, clippy::expect_used)]
230mod tests {
231 use super::*;
232
233 #[test]
236 #[cfg(feature = "material-icons")]
237 fn material_icons_cover_all_roles() {
238 for role in IconRole::ALL {
239 let svg = bundled_icon_svg(role, IconSet::Material);
240 assert!(svg.is_some(), "Material icons missing SVG for {:?}", role);
241 let bytes = svg.unwrap();
242 let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
243 assert!(
244 content.contains("<svg"),
245 "Material {:?} does not contain <svg tag",
246 role
247 );
248 }
249 }
250
251 #[test]
252 #[cfg(feature = "material-icons")]
253 fn material_icons_total_size_under_200kb() {
254 let total: usize = IconRole::ALL
255 .iter()
256 .filter_map(|role| bundled_icon_svg(*role, IconSet::Material))
257 .map(|svg| svg.len())
258 .sum();
259 assert!(
260 total < 400 * 1024,
261 "Material icons total size {} bytes exceeds 400KB budget",
262 total
263 );
264 }
265
266 #[test]
269 #[cfg(feature = "lucide-icons")]
270 fn lucide_icons_cover_all_roles() {
271 for role in IconRole::ALL {
272 let svg = bundled_icon_svg(role, IconSet::Lucide);
273 assert!(svg.is_some(), "Lucide icons missing SVG for {:?}", role);
274 let bytes = svg.unwrap();
275 let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
276 assert!(
277 content.contains("<svg"),
278 "Lucide {:?} does not contain <svg tag",
279 role
280 );
281 }
282 }
283
284 #[test]
285 #[cfg(feature = "lucide-icons")]
286 fn lucide_icons_total_size_under_100kb() {
287 let total: usize = IconRole::ALL
288 .iter()
289 .filter_map(|role| bundled_icon_svg(*role, IconSet::Lucide))
290 .map(|svg| svg.len())
291 .sum();
292 assert!(
293 total < 200 * 1024,
294 "Lucide icons total size {} bytes exceeds 200KB budget",
295 total
296 );
297 }
298
299 #[test]
302 fn non_bundled_sets_return_none() {
303 assert!(
304 bundled_icon_svg(IconRole::ActionCopy, IconSet::SfSymbols).is_none(),
305 "SfSymbols should not be a bundled set"
306 );
307 assert!(
308 bundled_icon_svg(IconRole::ActionCopy, IconSet::Freedesktop).is_none(),
309 "Freedesktop should not be a bundled set"
310 );
311 assert!(
312 bundled_icon_svg(IconRole::ActionCopy, IconSet::SegoeIcons).is_none(),
313 "SegoeIcons should not be a bundled set"
314 );
315 }
316
317 #[test]
322 #[cfg(all(feature = "lucide-icons", feature = "material-icons"))]
323 fn retired_bundle_names_return_none() {
324 for name in [
325 "close",
326 "dash",
327 "inspect",
328 "resize-corner",
329 "sort-ascending",
330 "sort-descending",
331 "window-close",
332 "window-maximize",
333 "window-minimize",
334 "window-restore",
335 "trash-2",
336 ] {
337 assert!(
338 bundled_icon_by_name(name, IconSet::Lucide).is_none(),
339 "{name} should be gone"
340 );
341 }
342 assert!(bundled_icon_by_name("star_border", IconSet::Material).is_none());
343 assert!(bundled_icon_by_name("font_size", IconSet::Material).is_none());
344 assert!(bundled_icon_by_name("trash", IconSet::Lucide).is_some());
345 assert!(bundled_icon_by_name("star_fill1", IconSet::Material).is_some());
346 assert!(bundled_icon_by_name("format_size", IconSet::Material).is_some());
347 }
348
349 #[test]
350 fn by_name_non_bundled_sets_return_none() {
351 assert!(bundled_icon_by_name("check", IconSet::SfSymbols).is_none());
352 assert!(bundled_icon_by_name("check", IconSet::Freedesktop).is_none());
353 assert!(bundled_icon_by_name("check", IconSet::SegoeIcons).is_none());
354 }
355
356 #[test]
357 fn by_name_unknown_name_returns_none() {
358 assert!(bundled_icon_by_name("nonexistent-icon-xyz", IconSet::Lucide).is_none());
359 assert!(bundled_icon_by_name("nonexistent_icon_xyz", IconSet::Material).is_none());
360 }
361
362 #[cfg(any(feature = "lucide-icons", feature = "material-icons"))]
363 fn bundled_files(dir: &str) -> Vec<String> {
364 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
365 .join("icons")
366 .join(dir);
367 let mut names: Vec<String> = std::fs::read_dir(&path)
368 .expect("icon directory exists")
369 .map(|entry| {
370 entry
371 .expect("readable entry")
372 .file_name()
373 .to_string_lossy()
374 .into_owned()
375 })
376 .filter_map(|file| file.strip_suffix(".svg").map(str::to_owned))
377 .collect();
378 names.sort();
379 assert!(!names.is_empty(), "icons/{dir} must not be empty");
380 names
381 }
382
383 #[test]
386 #[cfg(feature = "lucide-icons")]
387 fn generated_lucide_table_covers_every_bundled_file() {
388 for name in bundled_files("lucide") {
389 let svg = bundled_icon_by_name(&name, IconSet::Lucide)
390 .unwrap_or_else(|| panic!("Lucide table misses bundled file {name}.svg"));
391 assert!(std::str::from_utf8(svg).expect("UTF-8").contains("<svg"));
392 }
393 }
394
395 #[test]
396 #[cfg(feature = "material-icons")]
397 fn generated_material_table_covers_every_bundled_file() {
398 for name in bundled_files("material") {
399 let svg = bundled_icon_by_name(&name, IconSet::Material)
400 .unwrap_or_else(|| panic!("Material table misses bundled file {name}.svg"));
401 assert!(std::str::from_utf8(svg).expect("UTF-8").contains("<svg"));
402 }
403 }
404}