Skip to main content

cosmic/widget/icon/
handle.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use super::Icon;
5use crate::widget::{image, svg};
6use std::borrow::Cow;
7use std::ffi::OsStr;
8use std::fs::File;
9use std::hash::Hash;
10use std::io::Read;
11use std::path::{Path, PathBuf};
12
13#[must_use]
14#[derive(Clone, Debug, Hash, derive_setters::Setters)]
15pub struct Handle {
16    pub symbolic: bool,
17    #[setters(skip)]
18    pub data: Data,
19}
20
21impl Handle {
22    #[inline]
23    pub fn icon(self) -> Icon {
24        super::icon(self)
25    }
26}
27
28#[must_use]
29#[derive(Clone, Debug, Hash)]
30pub enum Data {
31    // Name(Named),
32    Image(image::Handle),
33    Svg(svg::Handle),
34}
35
36enum SvgSource {
37    Path,
38    Bytes(Vec<u8>),
39}
40
41fn svg_source(path: &Path) -> Option<SvgSource> {
42    if path
43        .extension()
44        .and_then(OsStr::to_str)
45        .is_some_and(|extension| extension.eq_ignore_ascii_case("svg"))
46    {
47        return Some(SvgSource::Path);
48    }
49
50    let Ok(mut file) = File::open(path) else {
51        return None;
52    };
53
54    let Ok(metadata) = file.metadata() else {
55        return None;
56    };
57    const MAX_SVG_SIZE: u64 = 16 * 1024 * 1024;
58    if !metadata.file_type().is_file() || metadata.len() > MAX_SVG_SIZE {
59        return None;
60    }
61
62    let mut prefix = [0; 32];
63    let Ok(length) = file.read(&mut prefix) else {
64        return None;
65    };
66    let prefix = &prefix[..length];
67
68    if ::image::guess_format(prefix).is_ok() {
69        return None;
70    }
71
72    let mut bytes = Vec::with_capacity(metadata.len() as usize);
73    bytes.extend_from_slice(prefix);
74    if file.read_to_end(&mut bytes).is_err() {
75        return None;
76    }
77
78    let document = roxmltree::Document::parse(std::str::from_utf8(&bytes).ok()?).ok()?;
79    (document.root_element().tag_name().name() == "svg").then_some(SvgSource::Bytes(bytes))
80}
81
82/// Create an icon handle from its path.
83pub fn from_path(path: PathBuf) -> Handle {
84    Handle {
85        symbolic: path
86            .file_stem()
87            .and_then(OsStr::to_str)
88            .is_some_and(|name| name.ends_with("-symbolic")),
89        data: match svg_source(&path) {
90            Some(SvgSource::Path) => Data::Svg(svg::Handle::from_path(path)),
91            Some(SvgSource::Bytes(bytes)) => Data::Svg(svg::Handle::from_memory(bytes)),
92            None => Data::Image(image::Handle::from_path(path)),
93        },
94    }
95}
96
97/// Create an image handle from memory.
98pub fn from_raster_bytes(
99    bytes: impl Into<Cow<'static, [u8]>>
100    + std::convert::AsRef<[u8]>
101    + std::marker::Send
102    + std::marker::Sync
103    + 'static,
104) -> Handle {
105    fn inner(bytes: Cow<'static, [u8]>) -> Handle {
106        Handle {
107            symbolic: false,
108            data: match bytes {
109                Cow::Owned(b) => Data::Image(image::Handle::from_bytes(b)),
110                Cow::Borrowed(b) => Data::Image(image::Handle::from_bytes(b)),
111            },
112        }
113    }
114
115    inner(bytes.into())
116}
117
118/// Create an image handle from RGBA data, where you must define the width and height.
119pub fn from_raster_pixels(
120    width: u32,
121    height: u32,
122    pixels: impl Into<Cow<'static, [u8]>>
123    + std::convert::AsRef<[u8]>
124    + std::marker::Send
125    + std::marker::Sync,
126) -> Handle {
127    fn inner(width: u32, height: u32, pixels: Cow<'static, [u8]>) -> Handle {
128        Handle {
129            symbolic: false,
130            data: match pixels {
131                Cow::Owned(pixels) => Data::Image(image::Handle::from_rgba(width, height, pixels)),
132                Cow::Borrowed(pixels) => {
133                    Data::Image(image::Handle::from_rgba(width, height, pixels))
134                }
135            },
136        }
137    }
138
139    inner(width, height, pixels.into())
140}
141
142/// Create a SVG handle from memory.
143pub fn from_svg_bytes(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
144    Handle {
145        symbolic: false,
146        data: Data::Svg(svg::Handle::from_memory(bytes)),
147    }
148}