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 Fr,
26 Unknown,
27}
28
29#[derive(Debug, Clone, PartialEq)]
30pub enum CssValue {
31 Keyword(CssIdent), Length(f32, Unit), Number(f32), String(String), Color(String), Function(String, Vec<Vec<CssValue>>),
43 List(Vec<CssValue>), }
45
46impl CssValue {
47 pub fn to_rgba_tuple(&self) -> Option<(u8, u8, u8, u8)> {
49 match self {
50 CssValue::Color(s) => parse_color(&format!("#{}", s)),
51 _ => None,
52 }
53 }
54}
55
56impl Unit {
57 pub(crate) fn as_str(self) -> &'static str {
58 match self {
59 Unit::Px => "px",
60 Unit::Cm => "cm",
61 Unit::Mm => "mm",
62 Unit::In => "in",
63 Unit::Pt => "pt",
64 Unit::Pc => "pc",
65
66 Unit::Em => "em",
67 Unit::Rem => "rem",
68
69 Unit::Percent => "%",
70
71 Unit::Vw => "vw",
72 Unit::Vh => "vh",
73 Unit::Vmin => "vmin",
74 Unit::Vmax => "vmax",
75
76 Unit::Deg => "deg",
77 Unit::Fr => "fr",
78 Unit::Unknown => "unknown",
79 }
80 }
81}
82
83impl std::fmt::Display for CssValue {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 match self {
88 CssValue::Keyword(keyword) => f.write_str(keyword),
89 CssValue::Length(value, unit) => write!(f, "{value}{}", unit.as_str()),
90 CssValue::Number(value) => write!(f, "{value}"),
91 CssValue::String(value) => write!(f, "\"{value}\""),
92 CssValue::Color(value) => write!(f, "#{value}"),
93 CssValue::Function(name, arguments) => {
94 let arguments = arguments
95 .iter()
96 .map(|argument| {
97 argument
98 .iter()
99 .map(CssValue::to_string)
100 .collect::<Vec<_>>()
101 .join(" ")
102 })
103 .collect::<Vec<_>>()
104 .join(", ");
105 write!(f, "{name}({arguments})")
106 }
107 CssValue::List(values) => {
108 let values = values
109 .iter()
110 .map(CssValue::to_string)
111 .collect::<Vec<_>>()
112 .join(" ");
113 f.write_str(&values)
114 }
115 }
116 }
117}
118
119fn parse_color(s: &str) -> Option<(u8, u8, u8, u8)> {
121 let s = s.trim();
122 if let Some(hex) = s.strip_prefix('#') {
123 match hex.len() {
124 3 => {
125 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok()?;
127 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok()?;
128 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok()?;
129 Some((r, g, b, 255))
130 }
131 4 => {
132 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok()?;
134 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok()?;
135 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok()?;
136 let a = u8::from_str_radix(&hex[3..4].repeat(2), 16).ok()?;
137 Some((r, g, b, a))
138 }
139 6 => {
140 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
142 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
143 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
144 Some((r, g, b, 255))
145 }
146 8 => {
147 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
149 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
150 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
151 let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
152 Some((r, g, b, a))
153 }
154 _ => None,
155 }
156 } else {
157 None
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn hex_invalid_returns_none() {
167 assert_eq!(CssValue::Color("".into()).to_rgba_tuple(), None);
169 assert_eq!(CssValue::Color("a".into()).to_rgba_tuple(), None);
170 assert_eq!(CssValue::Color("aa".into()).to_rgba_tuple(), None);
171 assert_eq!(CssValue::Color("aaaaa".into()).to_rgba_tuple(), None);
172 assert_eq!(CssValue::Color("aaaaaaa".into()).to_rgba_tuple(), None);
173 assert_eq!(CssValue::Color("aaaaaaaaa".into()).to_rgba_tuple(), None);
174 assert_eq!(CssValue::Color("zzz".into()).to_rgba_tuple(), None);
176 assert_eq!(CssValue::Color("gggggg".into()).to_rgba_tuple(), None);
177 }
178
179 #[test]
180 fn to_rgba_tuple_non_color_returns_none() {
181 assert_eq!(CssValue::Keyword("red".into()).to_rgba_tuple(), None);
182 assert_eq!(CssValue::Length(10.0, Unit::Px).to_rgba_tuple(), None);
183 assert_eq!(CssValue::Number(1.0).to_rgba_tuple(), None);
184 assert_eq!(CssValue::String("#fff".into()).to_rgba_tuple(), None);
185 assert_eq!(
186 CssValue::Function("rgb".into(), vec![]).to_rgba_tuple(),
187 None
188 );
189 }
190
191 #[test]
192 fn display_renders_css_source_text() {
193 assert_eq!(CssValue::Length(10.0, Unit::Px).to_string(), "10px");
194 assert_eq!(CssValue::Length(1.5, Unit::Percent).to_string(), "1.5%");
195 assert_eq!(CssValue::Number(0.75).to_string(), "0.75");
196 assert_eq!(CssValue::Keyword("auto".into()).to_string(), "auto");
197 assert_eq!(CssValue::Color("fff".into()).to_string(), "#fff");
198 assert_eq!(CssValue::String("a b".into()).to_string(), "\"a b\"");
199 assert_eq!(
200 CssValue::Function(
201 "rgb".into(),
202 vec![
203 vec![CssValue::Number(255.0)],
204 vec![CssValue::Number(0.0)],
205 vec![CssValue::Number(0.0)],
206 ]
207 )
208 .to_string(),
209 "rgb(255, 0, 0)"
210 );
211 assert_eq!(
212 CssValue::List(vec![
213 CssValue::Length(100.0, Unit::Px),
214 CssValue::Keyword("auto".into())
215 ])
216 .to_string(),
217 "100px auto"
218 );
219 }
220}