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