resource-fork-types 0.1.1

Support for reading common resource fork types in rust
Documentation
use std::collections::HashMap;

use binrw::{binread, BinRead};
use lazy_static::lazy_static;
use resource_fork::Resource;

lazy_static! {
    pub static ref WINDOW_COLOR_TABLE_NAMES: HashMap<usize, String> = {
        let mut map = HashMap::new();
        map.insert(0, "wContentColor".to_string());
        map.insert(1, "wFrameColor".to_string());
        map.insert(2, "wTextColor".to_string());
        map.insert(3, "wHiliteColor".to_string());
        map.insert(4, "wTitleBarColor".to_string());
        map.insert(5, "wHiliteColorLight".to_string());
        map.insert(6, "wHiliteColorDark".to_string());
        map.insert(7, "wTitleBarLight".to_string());
        map.insert(8, "wTitleBarDark".to_string());
        map.insert(9, "wDialogLight".to_string());
        map.insert(10, "wDialogDark".to_string());
        map.insert(11, "wTingeLight".to_string());
        map.insert(12, "wTingeDark".to_string());
        map
    };
    pub static ref CUSTOM_COLOR_TABLE_NAMES: HashMap<usize, String> = {
        let mut map = HashMap::new();

        map.insert(0, "cFrameColor".to_string());
        map.insert(1, "cBodyColor".to_string());
        map.insert(2, "extColor".to_string());
        map.insert(5, "cArrowsColorLight".to_string());
        map.insert(6, "cArrowsColorDark".to_string());
        map.insert(7, "cThumbLight".to_string());
        map.insert(8, "cThumbDark".to_string());
        map.insert(9, "cHiliteLight".to_string());
        map.insert(10, "cHiliteDark".to_string());
        map.insert(11, "cTitleBarLight".to_string());
        map.insert(12, "cTitleBarDark".to_string());
        map.insert(13, "cTingeLight".to_string());
        map.insert(14, "cTingeDark".to_string());

        map
    };
}

#[derive(BinRead)]
#[br(big)]
struct ClutHeader {
    _unknown: [u16; 3],
    #[br(map(|v: u16| v.into()))]
    max_color_idx: usize,
}

#[binread]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[serde(into = "ClutEntrySerde")]
#[br(big)]
pub struct ClutEntry {
    #[serde(skip)]
    color_id: u16,
    pub red: u16,
    pub green: u16,
    pub blue: u16,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[serde(into = "ClutEntrySerde")]
pub struct NamedClutEntry {
    pub red: u16,
    pub green: u16,
    pub blue: u16,
    pub name: Option<String>,
}

impl From<NamedClutEntry> for ClutEntrySerde {
    fn from(val: NamedClutEntry) -> Self {
        ClutEntrySerde {
            red: val.red,
            green: val.green,
            blue: val.blue,
            hex: format!(
                "#{:02x}{:02x}{:02x}",
                ((val.red as f32 / u16::MAX as f32) * 0xFF as f32) as u8,
                ((val.green as f32 / u16::MAX as f32) * 0xFF as f32) as u8,
                ((val.blue as f32 / u16::MAX as f32) * 0xFF as f32) as u8
            ),
            name: val.name.clone(),
        }
    }
}

impl From<ClutEntry> for ClutEntrySerde {
    fn from(val: ClutEntry) -> Self {
        ClutEntrySerde {
            red: val.red,
            green: val.green,
            blue: val.blue,
            hex: format!(
                "#{:02x}{:02x}{:02x}",
                ((val.red as f32 / u16::MAX as f32) * 0xFF as f32) as u8,
                ((val.green as f32 / u16::MAX as f32) * 0xFF as f32) as u8,
                ((val.blue as f32 / u16::MAX as f32) * 0xFF as f32) as u8
            ),
            name: None,
        }
    }
}

#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ClutEntrySerde {
    pub red: u16,
    pub green: u16,
    pub blue: u16,
    pub hex: String,
    pub name: Option<String>,
}

impl ClutEntry {
    pub fn to_rgb(&self) -> [u8; 3] {
        [
            ((self.red >> 8) & 0xFF) as u8,
            ((self.green >> 8) & 0xFF) as u8,
            ((self.blue >> 8) & 0xFF) as u8,
        ]
    }

    pub fn with_name(&self, name: Option<String>) -> NamedClutEntry {
        NamedClutEntry {
            red: self.red,
            green: self.green,
            blue: self.blue,
            name,
        }
    }
}

impl std::fmt::Display for ClutEntry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let [red, green, blue] = self.to_rgb();
        write!(f, "Rgb(0x{red:02x}, 0x{green:02x}, 0x{blue:02x})")
    }
}

#[binread]
#[derive(Resource)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[resource(code = "clut")]
pub struct Clut {
    #[br(temp)]
    header: ClutHeader,

    #[br(count(header.max_color_idx + 1))]
    pub colors: Vec<ClutEntry>,
}

impl Clut {
    pub fn rgb_colors(&self) -> Vec<[u8; 3]> {
        self.colors.iter().map(|c| c.to_rgb()).collect()
    }

    pub fn with_names(&self, names: &HashMap<usize, String>) -> NamedClut {
        NamedClut {
            colors: self
                .colors
                .iter()
                .enumerate()
                .map(|(idx, c)| c.with_name(names.get(&idx).cloned()))
                .collect(),
        }
    }
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NamedClut {
    pub colors: Vec<NamedClutEntry>,
}

#[binread]
#[derive(Resource)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[resource(code = "cctb")]
pub struct CustomColorTable(Clut);

#[cfg(feature = "serde")]
impl serde::Serialize for CustomColorTable {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.0
            .with_names(&CUSTOM_COLOR_TABLE_NAMES)
            .serialize(serializer)
    }
}