orinium_browser/engine/css/
values.rs1pub type CssIdent = smol_str::SmolStr;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum Unit {
7 Px,
8 Cm,
9 Mm,
10 In,
11 Pt,
12 Pc,
13
14 Em,
15 Rem,
16
17 Percent,
18
19 Vw,
20 Vh,
21 Vmin,
22 Vmax,
23
24 Deg,
25 Rad,
26 Grad,
27 Turn,
28
29 S,
30 Ms,
31
32 Fr,
33 Unknown,
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub enum CssValue {
38 Keyword(CssIdent), Length(f32, Unit), Number(f32), String(String), Color(String), Function(String, Vec<Vec<CssValue>>),
50 List(Vec<CssValue>), Separator,
57}
58
59impl CssValue {
60 pub fn to_rgba_tuple(&self) -> Option<(u8, u8, u8, u8)> {
62 match self {
63 CssValue::Color(s) => parse_color(&format!("#{}", s)),
64 _ => None,
65 }
66 }
67}
68
69impl Unit {
70 pub(crate) fn as_str(self) -> &'static str {
71 match self {
72 Unit::Px => "px",
73 Unit::Cm => "cm",
74 Unit::Mm => "mm",
75 Unit::In => "in",
76 Unit::Pt => "pt",
77 Unit::Pc => "pc",
78
79 Unit::Em => "em",
80 Unit::Rem => "rem",
81
82 Unit::Percent => "%",
83
84 Unit::Vw => "vw",
85 Unit::Vh => "vh",
86 Unit::Vmin => "vmin",
87 Unit::Vmax => "vmax",
88
89 Unit::Deg => "deg",
90 Unit::Rad => "rad",
91 Unit::Grad => "grad",
92 Unit::Turn => "turn",
93
94 Unit::S => "s",
95 Unit::Ms => "ms",
96
97 Unit::Fr => "fr",
98 Unit::Unknown => "unknown",
99 }
100 }
101}
102
103impl std::fmt::Display for CssValue {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 match self {
108 CssValue::Keyword(keyword) => f.write_str(keyword),
109 CssValue::Length(value, unit) => write!(f, "{value}{}", unit.as_str()),
110 CssValue::Number(value) => write!(f, "{value}"),
111 CssValue::String(value) => write!(f, "\"{value}\""),
112 CssValue::Color(value) => write!(f, "#{value}"),
113 CssValue::Function(name, arguments) => {
114 let arguments = arguments
115 .iter()
116 .map(|argument| {
117 argument
118 .iter()
119 .map(CssValue::to_string)
120 .collect::<Vec<_>>()
121 .join(" ")
122 })
123 .collect::<Vec<_>>()
124 .join(", ");
125 write!(f, "{name}({arguments})")
126 }
127 CssValue::List(values) => {
128 let values = values
129 .iter()
130 .map(CssValue::to_string)
131 .collect::<Vec<_>>()
132 .join(" ");
133 f.write_str(&values)
134 }
135 CssValue::Separator => f.write_str(", "),
136 }
137 }
138}
139
140fn parse_color(s: &str) -> Option<(u8, u8, u8, u8)> {
142 let s = s.trim();
143 if let Some(hex) = s.strip_prefix('#') {
144 match hex.len() {
145 3 => {
146 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok()?;
148 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok()?;
149 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok()?;
150 Some((r, g, b, 255))
151 }
152 4 => {
153 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok()?;
155 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok()?;
156 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok()?;
157 let a = u8::from_str_radix(&hex[3..4].repeat(2), 16).ok()?;
158 Some((r, g, b, a))
159 }
160 6 => {
161 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
163 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
164 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
165 Some((r, g, b, 255))
166 }
167 8 => {
168 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
170 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
171 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
172 let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
173 Some((r, g, b, a))
174 }
175 _ => None,
176 }
177 } else {
178 None
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn hex_invalid_returns_none() {
188 assert_eq!(CssValue::Color("".into()).to_rgba_tuple(), None);
190 assert_eq!(CssValue::Color("a".into()).to_rgba_tuple(), None);
191 assert_eq!(CssValue::Color("aa".into()).to_rgba_tuple(), None);
192 assert_eq!(CssValue::Color("aaaaa".into()).to_rgba_tuple(), None);
193 assert_eq!(CssValue::Color("aaaaaaa".into()).to_rgba_tuple(), None);
194 assert_eq!(CssValue::Color("aaaaaaaaa".into()).to_rgba_tuple(), None);
195 assert_eq!(CssValue::Color("zzz".into()).to_rgba_tuple(), None);
197 assert_eq!(CssValue::Color("gggggg".into()).to_rgba_tuple(), None);
198 }
199
200 #[test]
201 fn to_rgba_tuple_non_color_returns_none() {
202 assert_eq!(CssValue::Keyword("red".into()).to_rgba_tuple(), None);
203 assert_eq!(CssValue::Length(10.0, Unit::Px).to_rgba_tuple(), None);
204 assert_eq!(CssValue::Number(1.0).to_rgba_tuple(), None);
205 assert_eq!(CssValue::String("#fff".into()).to_rgba_tuple(), None);
206 assert_eq!(
207 CssValue::Function("rgb".into(), vec![]).to_rgba_tuple(),
208 None
209 );
210 }
211
212 #[test]
213 fn display_renders_css_source_text() {
214 assert_eq!(CssValue::Length(10.0, Unit::Px).to_string(), "10px");
215 assert_eq!(CssValue::Length(1.5, Unit::Percent).to_string(), "1.5%");
216 assert_eq!(CssValue::Number(0.75).to_string(), "0.75");
217 assert_eq!(CssValue::Keyword("auto".into()).to_string(), "auto");
218 assert_eq!(CssValue::Color("fff".into()).to_string(), "#fff");
219 assert_eq!(CssValue::String("a b".into()).to_string(), "\"a b\"");
220 assert_eq!(
221 CssValue::Function(
222 "rgb".into(),
223 vec![
224 vec![CssValue::Number(255.0)],
225 vec![CssValue::Number(0.0)],
226 vec![CssValue::Number(0.0)],
227 ]
228 )
229 .to_string(),
230 "rgb(255, 0, 0)"
231 );
232 assert_eq!(
233 CssValue::List(vec![
234 CssValue::Length(100.0, Unit::Px),
235 CssValue::Keyword("auto".into())
236 ])
237 .to_string(),
238 "100px auto"
239 );
240 }
241}