yatp_cli/
dictionary.rs

1//!
2//! Structures used to store data about where a given texture was packed in the texture atlas.
3//!
4
5use std::path::PathBuf;
6
7use serde::Serialize;
8
9use crate::{format, Size};
10
11/// Rectangle
12#[derive(Debug, Serialize, Clone, Copy)]
13pub struct Rect {
14    /// Top-left X
15    pub x: u32,
16    /// Top-left Y
17    pub y: u32,
18    /// Width
19    pub width: u32,
20    /// Height
21    pub height: u32,
22}
23
24/// Entry into a `Dictionary`
25#[derive(Debug, Serialize, Clone)]
26pub struct Entry {
27    /// Name of the dictionary entry, usually the file stem with a suffix
28    pub name: String,
29    /// Path of the dictionary entry
30    pub path: String,
31    /// Rectangle
32    pub rect: Rect,
33}
34
35/// Dictionary containing data about the packed textures and texture atlas, serialized into a given format.
36#[derive(Debug, Serialize)]
37pub struct Dictionary {
38    /// Width of the atlas
39    width: u32,
40    /// Height of the atlas
41    height: u32,
42    /// List of entries recorded
43    items: Vec<Entry>,
44}
45
46impl Dictionary {
47    /// Creates a new dictionary with given atlas `size` and no entries
48    pub fn new(size: Size) -> Self {
49        Self {
50            width: size.width,
51            height: size.height,
52            items: vec![],
53        }
54    }
55
56    /// Records a new entry into the dictionary with provided `path` and `rect` (rectangle).
57    /// NOTE: name of the entry is derived from its path and suffixes may be appended to avoid
58    /// conflicting names
59    #[allow(clippy::ptr_arg)]
60    pub fn record(&mut self, path: &PathBuf, rect: &crate::Rect) -> Entry {
61        let name = path
62            .file_stem()
63            .or_else(|| path.file_name())
64            .unwrap_or(path.as_os_str())
65            .to_string_lossy()
66            .to_string();
67
68        let entry = Entry {
69            name: self.pick_name(&name),
70            path: path.to_string_lossy().to_string(),
71            rect: self::Rect {
72                x: rect.origin.x,
73                y: rect.origin.y,
74                width: rect.size.width,
75                height: rect.size.height,
76            },
77        };
78
79        self.items.push(entry.clone());
80        entry
81    }
82
83    fn pick_name(&self, name: &str) -> String {
84        let exists = |name: &str| self.items.iter().any(|v| v.name == name);
85
86        if !exists(name) {
87            return String::from(name);
88        }
89
90        for i in 0.. {
91            let new_name = format!("{}-{}", name, i);
92
93            if !exists(&new_name) {
94                return new_name;
95            }
96        }
97
98        String::from(name)
99    }
100
101    /// Serializes the dictionary to a file with given `name` and `dict` format
102    pub fn save(&self, name: &str, dict: format::DictionaryFormat) -> anyhow::Result<()> {
103        let content = match dict {
104            format::DictionaryFormat::Toml => toml::to_vec(self)?,
105            format::DictionaryFormat::Json => serde_json::to_vec_pretty(self)?,
106            format::DictionaryFormat::Yaml => serde_yaml::to_string(self)?.into_bytes(),
107            format::DictionaryFormat::Ron => ron::to_string(self)?.into_bytes(),
108        };
109        std::fs::write(format!("{}.{}", name, dict.ext()), content)?;
110        Ok(())
111    }
112}