1use std::error::Error;
16use std::ffi::CStr;
17use std::path::Path;
18use zerocopy::FromBytes;
19
20#[cfg(feature = "file")]
21pub mod file;
22pub mod raw;
23
24#[derive(derive_more::Debug, Copy, Clone)]
31pub struct IconCache<'a> {
32 #[debug(skip)]
34 pub bytes: &'a [u8],
35 pub header: &'a raw::Header,
37 pub hash: &'a raw::Hash,
39 pub directory_list: DirectoryList<'a>,
41}
42
43impl<'a> IconCache<'a> {
44 pub fn new_from_bytes(bytes: &'a [u8]) -> Result<Self, Box<dyn Error + 'a>> {
45 let (header, _) = raw::Header::ref_from_prefix(bytes)?;
46
47 let hash = header.hash.at(bytes)?;
48 let directory_list = header.directory_list.at(bytes)?;
49 let directory_list = DirectoryList {
50 bytes,
51 raw_list: directory_list,
52 };
53
54 Ok(IconCache {
55 bytes,
56 header,
57 hash,
58 directory_list,
59 })
60 }
61
62 pub fn icon(&self, icon_name: impl AsRef<[u8]>) -> Option<Icon<'a>> {
67 let icon_name = icon_name.as_ref();
68 let hash = icon_str_hash(icon_name);
69 let n_buckets = self.hash.n_buckets.get();
70 let bucket = hash % n_buckets;
71
72 let icons = self.icon_chain(bucket)?.iter(self.bytes);
73
74 for icon in icons {
75 let Ok(name) = icon.name.str_at(self.bytes) else {
76 continue;
77 };
78
79 if name.to_bytes() == icon_name {
80 return Some(Icon {
81 name,
82 image_list: ImageList::from_icon(icon, self.bytes)?,
83 });
84 }
85 }
86
87 None
88 }
89
90 pub fn iter(&self) -> impl Iterator<Item = Icon<'a>> {
91 (0..self.hash.n_buckets.get())
92 .filter_map(|bucket| self.icon_chain(bucket))
93 .flat_map(|chain| chain.iter(self.bytes))
94 .filter_map(|icon| {
95 Some(Icon {
96 name: icon.name.str_at(self.bytes).ok()?,
97 image_list: ImageList::from_icon(icon, self.bytes)?,
98 })
99 })
100 }
101
102 fn icon_chain(&self, bucket: u32) -> Option<&'a raw::Icon> {
103 debug_assert!(bucket < self.hash.n_buckets.get());
104
105 let offset = self.hash.icon[bucket as usize];
106 if offset.is_null() {
108 return None;
109 }
110
111 offset.at(self.bytes).ok()
112 }
113}
114
115#[derive(derive_more::Debug, Copy, Clone)]
117pub struct DirectoryList<'a> {
118 #[debug(skip)]
119 #[allow(unused)] bytes: &'a [u8],
121 pub raw_list: &'a raw::DirectoryList,
122}
123
124impl<'a> DirectoryList<'a> {
125 #[inline(always)]
127 pub fn len(&self) -> u32 {
128 self.raw_list.n_directories.get()
129 }
130
131 pub fn is_empty(&self) -> bool {
133 self.len() == 0
134 }
135
136 pub fn dir(&self, idx: u32) -> Option<&'a Path> {
140 if idx >= self.len() {
141 return None;
142 }
143
144 self.raw_list.directory[idx as usize].path_at(self.bytes)
145 }
146
147 pub fn iter(&self) -> impl Iterator<Item = &'a Path> {
149 (0..self.len()).filter_map(|idx| self.dir(idx))
150 }
151}
152
153#[derive(Debug, Copy, Clone)]
155pub struct Icon<'a> {
156 pub name: &'a CStr,
157 pub image_list: ImageList<'a>,
158}
159
160#[derive(derive_more::Debug, Copy, Clone)]
161pub struct ImageList<'a> {
162 #[debug(skip)]
163 bytes: &'a [u8],
164 pub raw_list: &'a raw::ImageList,
165}
166
167impl<'a> ImageList<'a> {
168 fn from_icon(icon: &raw::Icon, bytes: &'a [u8]) -> Option<ImageList<'a>> {
169 Some(Self {
170 bytes,
171 raw_list: icon.image_list.at(bytes).ok()?,
172 })
173 }
174
175 pub fn len(&self) -> u32 {
177 self.raw_list.n_images.get()
178 }
179
180 pub fn is_empty(&self) -> bool {
182 self.len() == 0
183 }
184
185 pub fn image(&self, idx: u32) -> Option<Image<'a>> {
190 if idx >= self.len() {
191 return None;
192 }
193
194 let raw_image = &self.raw_list.images[idx as usize];
195
196 let (header, _) = raw::Header::ref_from_prefix(self.bytes).ok()?;
199 let directory_list = header.directory_list.at(self.bytes).ok()?;
200 let directory = directory_list.directory[raw_image.directory_index.get() as usize]
201 .path_at(self.bytes)?;
202
203 let icon_flags = raw_image.icon_flags;
204
205 let mut image_data = None;
206
207 if raw_image.image_data.offset != 0 {
208 let &raw::ImageData {
209 image_pixel_data,
210 image_meta_data,
211 image_pixel_data_length,
212 image_pixel_data_type,
213 } = raw_image.image_data.at(self.bytes).ok()?;
214
215 image_data = Some(ImageData {
216 image_pixel_data: *image_pixel_data.at(self.bytes).ok()?,
217 image_meta_data: image_meta_data.at(self.bytes).ok()?,
218 image_pixel_data_type: *image_pixel_data_type.at(self.bytes).ok()?,
219 image_pixel_data_length: *image_pixel_data_length.at(self.bytes).ok()?,
220 });
221 }
222
223 Some(Image {
224 directory,
225 icon_flags,
226 image_data,
227 })
228 }
229
230 pub fn iter(&self) -> impl Iterator<Item = Image<'a>> {
232 (0..self.len()).filter_map(|idx| self.image(idx))
233 }
234}
235
236#[derive(derive_more::Debug, Copy, Clone)]
237pub struct Image<'a> {
238 pub directory: &'a Path,
239 pub icon_flags: raw::Flags,
240 pub image_data: Option<ImageData<'a>>,
241}
242
243#[derive(derive_more::Debug, Copy, Clone)]
244pub struct ImageData<'a> {
245 pub image_pixel_data: (), pub image_meta_data: &'a raw::MetaData,
247 pub image_pixel_data_type: (),
248 pub image_pixel_data_length: (),
249}
250
251fn icon_str_hash(key: impl AsRef<[u8]>) -> u32 {
252 let bytes = key.as_ref();
253
254 if bytes.is_empty() {
255 return 0;
256 }
257
258 let mut h: u32 = bytes[0] as u32;
259 for &p in &bytes[1..] {
260 h = (h << 5).overflowing_sub(h).0.overflowing_add(p as u32).0;
261 }
262
263 h
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use crate::raw::Offset;
270 use zerocopy::network_endian::U16;
271
272 static SAMPLE_INDEX_FILE: &[u8] = include_bytes!("../assets/icon-theme.cache");
275
276 #[test]
277 fn test_find_specific_icon() -> Result<(), Box<dyn Error>> {
278 let cache = IconCache::new_from_bytes(SAMPLE_INDEX_FILE)?;
279
280 assert_eq!(
281 cache.header,
282 &raw::Header {
283 major_version: U16::new(1),
284 minor_version: U16::new(0),
285 hash: Offset::new(12),
286 directory_list: Offset::new(35812)
287 }
288 );
289
290 assert_eq!(cache.hash.n_buckets, 251);
291
292 let icon = cache.icon("mpv").unwrap();
293
294 assert_eq!(icon.name.to_str(), Ok("mpv"));
295 assert_eq!(icon.image_list.len(), 5);
296
297 let image = &icon.image_list.image(0).unwrap();
298
299 assert_eq!(image.directory.to_str(), Some("scalable/apps"));
300 assert_eq!(
301 image.icon_flags,
302 raw::Flags::new(raw::Flags::HAS_SUFFIX_SVG)
303 );
304 assert!(image.image_data.is_none());
305
306 Ok(())
307 }
308
309 #[test]
310 fn test_icon_iter() -> Result<(), Box<dyn Error>> {
311 let cache = IconCache::new_from_bytes(SAMPLE_INDEX_FILE)?;
312
313 assert_eq!(cache.iter().count(), 563);
314
315 Ok(())
316 }
317
318 #[test]
319 fn test_image_list_iter() -> Result<(), Box<dyn Error>> {
320 let cache = IconCache::new_from_bytes(SAMPLE_INDEX_FILE)?;
321 let icon = cache.icon("mpv").unwrap();
322
323 let count = icon.image_list.iter().count();
324 assert_eq!(count, 5);
325
326 Ok(())
327 }
328
329 #[test]
330 fn test_directory_list_iter() -> Result<(), Box<dyn Error>> {
331 let cache = IconCache::new_from_bytes(SAMPLE_INDEX_FILE)?;
332 let dir_list = cache.directory_list;
333
334 assert_eq!(dir_list.len(), 59);
335
336 assert!(!cache.directory_list.is_empty());
337 assert_eq!(cache.directory_list.iter().count(), 59);
338
339 Ok(())
340 }
341
342 #[test]
343 fn icon_str_hash_empty() {
344 assert_eq!(icon_str_hash(""), 0);
345 }
346
347 #[test]
348 fn icon_str_hash_hello_world() {
349 assert_eq!(icon_str_hash("hello world"), 1794106052);
350 }
351
352 #[test]
353 fn icon_str_hash_sym() {
354 assert_eq!(icon_str_hash("preferences-other-symbolic") % 251, 243);
355 }
356
357 #[test]
358 fn image_size_correct() {
359 assert_eq!(size_of::<raw::Image>(), 8);
360 }
361}