swash/
setting.rs

1use super::{tag_from_bytes, tag_from_str_lossy, Tag};
2use core::fmt;
3
4/// Setting combining a tag and a value for features and variations.
5#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
6pub struct Setting<T> {
7    /// The tag that identifies the setting.
8    pub tag: Tag,
9    /// The value for the setting.
10    pub value: T,
11}
12
13impl<T: fmt::Display> fmt::Display for Setting<T> {
14    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15        let bytes = self.tag.to_be_bytes();
16        let tag_name = core::str::from_utf8(&bytes).unwrap_or("");
17        write!(f, "\"{}\" {}", tag_name, self.value)
18    }
19}
20
21impl Setting<u16> {
22    /// Parses a feature setting according to the CSS grammar.
23    pub fn parse(s: &str) -> Option<Self> {
24        Self::parse_list(s).next()
25    }
26
27    /// Parses a comma separated list of feature settings according to the CSS
28    /// grammar.
29    pub fn parse_list(s: &str) -> impl Iterator<Item = Self> + '_ + Clone {
30        ParseList::new(s)
31            .map(|(_, tag, value_str)| {
32                let (ok, value) = match value_str {
33                    "on" | "" => (true, 1),
34                    "off" => (true, 0),
35                    _ => match value_str.parse::<u16>() {
36                        Ok(value) => (true, value),
37                        _ => (false, 0),
38                    },
39                };
40                (ok, tag, value)
41            })
42            .take_while(|(ok, _, _)| *ok)
43            .map(|(_, tag, value)| Self { tag, value })
44    }
45}
46
47impl Setting<f32> {
48    /// Parses a variation setting according to the CSS grammar.    
49    pub fn parse(s: &str) -> Option<Self> {
50        Self::parse_list(s).next()
51    }
52
53    /// Parses a comma separated list of variation settings according to the
54    /// CSS grammar.    
55    pub fn parse_list(s: &str) -> impl Iterator<Item = Self> + '_ + Clone {
56        ParseList::new(s)
57            .map(|(_, tag, value_str)| {
58                let (ok, value) = match value_str.parse::<f32>() {
59                    Ok(value) => (true, value),
60                    _ => (false, 0.),
61                };
62                (ok, tag, value)
63            })
64            .take_while(|(ok, _, _)| *ok)
65            .map(|(_, tag, value)| Self { tag, value })
66    }
67}
68
69impl<T> From<(Tag, T)> for Setting<T> {
70    fn from(v: (Tag, T)) -> Self {
71        Self {
72            tag: v.0,
73            value: v.1,
74        }
75    }
76}
77
78impl<T: Copy> From<&(Tag, T)> for Setting<T> {
79    fn from(v: &(Tag, T)) -> Self {
80        Self {
81            tag: v.0,
82            value: v.1,
83        }
84    }
85}
86
87impl<T: Copy> From<&([u8; 4], T)> for Setting<T> {
88    fn from(v: &([u8; 4], T)) -> Self {
89        Self {
90            tag: tag_from_bytes(&v.0),
91            value: v.1,
92        }
93    }
94}
95
96impl<T: Copy> From<&(&[u8; 4], T)> for Setting<T> {
97    fn from(v: &(&[u8; 4], T)) -> Self {
98        Self {
99            tag: tag_from_bytes(v.0),
100            value: v.1,
101        }
102    }
103}
104
105impl<T> From<(&str, T)> for Setting<T> {
106    fn from(v: (&str, T)) -> Self {
107        Self {
108            tag: tag_from_str_lossy(v.0),
109            value: v.1,
110        }
111    }
112}
113
114impl<T: Copy> From<&(&str, T)> for Setting<T> {
115    fn from(v: &(&str, T)) -> Self {
116        Self {
117            tag: tag_from_str_lossy(v.0),
118            value: v.1,
119        }
120    }
121}
122
123#[derive(Clone)]
124struct ParseList<'a> {
125    source: &'a [u8],
126    len: usize,
127    pos: usize,
128}
129
130impl<'a> ParseList<'a> {
131    fn new(source: &'a str) -> Self {
132        Self {
133            source: source.as_bytes(),
134            len: source.len(),
135            pos: 0,
136        }
137    }
138}
139
140impl<'a> Iterator for ParseList<'a> {
141    type Item = (usize, Tag, &'a str);
142
143    fn next(&mut self) -> Option<Self::Item> {
144        let mut pos = self.pos;
145        while pos < self.len && {
146            let ch = self.source[pos];
147            ch.is_ascii_whitespace() || ch == b','
148        } {
149            pos += 1;
150        }
151        self.pos = pos;
152        if pos >= self.len {
153            return None;
154        }
155        let first = self.source[pos];
156        let mut start = pos;
157        let quote = match first {
158            b'"' | b'\'' => {
159                pos += 1;
160                start += 1;
161                first
162            }
163            _ => return None,
164        };
165        let mut tag_str = None;
166        while pos < self.len {
167            if self.source[pos] == quote {
168                tag_str = core::str::from_utf8(self.source.get(start..pos)?).ok();
169                pos += 1;
170                break;
171            }
172            pos += 1;
173        }
174        self.pos = pos;
175        let tag_str = tag_str?;
176        if tag_str.len() != 4 || !tag_str.is_ascii() {
177            return None;
178        }
179        let tag = tag_from_str_lossy(tag_str);
180        while pos < self.len {
181            if !self.source[pos].is_ascii_whitespace() {
182                break;
183            }
184            pos += 1;
185        }
186        self.pos = pos;
187        start = pos;
188        let mut end = start;
189        while pos < self.len {
190            if self.source[pos] == b',' {
191                pos += 1;
192                break;
193            }
194            pos += 1;
195            end += 1;
196        }
197        let value = core::str::from_utf8(self.source.get(start..end)?)
198            .ok()?
199            .trim();
200        self.pos = pos;
201        Some((pos, tag, value))
202    }
203}