Skip to main content

asc_hap/resource/
qualifier.rs

1//! Qualifiers: what a resource directory name (`base`, `zh_CN`,
2//! `en_US-vertical-phone-dark`) compiles to.
3
4use std::fmt;
5
6/// What a qualifier restricts.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum QualifierKey {
9    Language,
10    Region,
11    /// Screen density (`sdpi` .. `xxxldpi`), stored as dots per inch.
12    Density,
13    Orientation,
14    Device,
15    Script,
16    /// `dark` / `light`.
17    ColorMode,
18    Mcc,
19    Mnc,
20    InputDevice,
21    /// A key this crate has no name for.
22    Other(u32),
23}
24
25impl QualifierKey {
26    fn from_code(code: u32) -> QualifierKey {
27        match code {
28            0 => QualifierKey::Language,
29            1 => QualifierKey::Region,
30            2 => QualifierKey::Density,
31            3 => QualifierKey::Orientation,
32            4 => QualifierKey::Device,
33            5 => QualifierKey::Script,
34            6 => QualifierKey::ColorMode,
35            7 => QualifierKey::Mcc,
36            8 => QualifierKey::Mnc,
37            10 => QualifierKey::InputDevice,
38            other => QualifierKey::Other(other),
39        }
40    }
41}
42
43/// The key's name: `language`, `colorMode`, `key9` for an unnamed one.
44impl fmt::Display for QualifierKey {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        f.write_str(match self {
47            QualifierKey::Language => "language",
48            QualifierKey::Region => "region",
49            QualifierKey::Density => "density",
50            QualifierKey::Orientation => "orientation",
51            QualifierKey::Device => "device",
52            QualifierKey::Script => "script",
53            QualifierKey::ColorMode => "colorMode",
54            QualifierKey::Mcc => "mcc",
55            QualifierKey::Mnc => "mnc",
56            QualifierKey::InputDevice => "inputDevice",
57            QualifierKey::Other(code) => return write!(f, "key{code}"),
58        })
59    }
60}
61
62/// One key of a qualifier set with its stored value.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub struct Qualifier {
65    pub key: QualifierKey,
66    /// The stored number: packed letters for language, script and region,
67    /// dots per inch for density, an enumerator for the rest.
68    pub value: u32,
69}
70
71impl Qualifier {
72    pub(super) fn from_raw(key: u32, value: u32) -> Qualifier {
73        Qualifier { key: QualifierKey::from_code(key), value }
74    }
75
76    /// Language, script and region pack their letters into the number,
77    /// first letter in the highest used byte: `zh` is `0x7a68`.
78    fn letters(self) -> String {
79        self.value.to_be_bytes().into_iter().filter(|&b| b != 0).map(|b| if b.is_ascii_graphic() { char::from(b) } else { '?' }).collect()
80    }
81
82    fn enumerator(self, names: &[(u32, &'static str)]) -> Option<&'static str> {
83        names.iter().find(|(value, _)| *value == self.value).map(|(_, name)| *name)
84    }
85}
86
87const DENSITIES: &[(u32, &str)] = &[(120, "sdpi"), (160, "mdpi"), (240, "ldpi"), (320, "xldpi"), (480, "xxldpi"), (640, "xxxldpi")];
88const ORIENTATIONS: &[(u32, &str)] = &[(0, "vertical"), (1, "horizontal")];
89const DEVICES: &[(u32, &str)] = &[(0, "phone"), (1, "tablet"), (2, "car"), (3, "pad"), (4, "tv"), (6, "wearable"), (7, "2in1")];
90const COLOR_MODES: &[(u32, &str)] = &[(0, "dark"), (1, "light")];
91const INPUT_DEVICES: &[(u32, &str)] = &[(0, "pointingdevice")];
92
93/// The value as it is spelled in a resource directory name: `zh`, `CN`,
94/// `dark`, `xldpi`, `mcc460`. A value without a spelling is
95/// `<key>=<number>`.
96impl fmt::Display for Qualifier {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        let named = match self.key {
99            QualifierKey::Language | QualifierKey::Script | QualifierKey::Region => return f.write_str(&self.letters()),
100            QualifierKey::Mcc => return write!(f, "mcc{}", self.value),
101            QualifierKey::Mnc => return write!(f, "mnc{}", self.value),
102            QualifierKey::Density => self.enumerator(DENSITIES),
103            QualifierKey::Orientation => self.enumerator(ORIENTATIONS),
104            QualifierKey::Device => self.enumerator(DEVICES),
105            QualifierKey::ColorMode => self.enumerator(COLOR_MODES),
106            QualifierKey::InputDevice => self.enumerator(INPUT_DEVICES),
107            QualifierKey::Other(_) => None,
108        };
109        match named {
110            Some(name) => f.write_str(name),
111            None => write!(f, "{}={}", self.key, self.value),
112        }
113    }
114}
115
116/// The qualifiers one value of a resource was defined under. Empty is the
117/// `base` directory, the value used when nothing more specific matches.
118#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
119pub struct Qualifiers {
120    params: Vec<Qualifier>,
121}
122
123impl Qualifiers {
124    pub(super) fn new(params: Vec<Qualifier>) -> Qualifiers {
125        Qualifiers { params }
126    }
127
128    /// The keys in stored order.
129    pub fn params(&self) -> &[Qualifier] {
130        &self.params
131    }
132
133    /// Whether this is the unqualified `base` set.
134    pub fn is_base(&self) -> bool {
135        self.params.is_empty()
136    }
137
138    /// The stored value of `key`, when the set restricts it.
139    pub fn get(&self, key: QualifierKey) -> Option<u32> {
140        self.params.iter().find(|p| p.key == key).map(|p| p.value)
141    }
142}
143
144/// The directory name: `base`, `zh_CN`, `en_US-vertical-phone-dark`.
145/// Script, region and mnc attach to what precedes them with `_`, every
146/// other key with `-`.
147impl fmt::Display for Qualifiers {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        if self.params.is_empty() {
150            return f.write_str("base");
151        }
152        for (i, param) in self.params.iter().enumerate() {
153            let attaches = matches!(param.key, QualifierKey::Script | QualifierKey::Region | QualifierKey::Mnc);
154            if i > 0 {
155                f.write_str(if attaches { "_" } else { "-" })?;
156            }
157            write!(f, "{param}")?;
158        }
159        Ok(())
160    }
161}