stylex_css_parser/css_types/
length.rs1use crate::{token_parser::TokenParser, token_types::SimpleToken};
8use std::fmt::{self, Display};
9
10pub const UNITS_BASED_ON_FONT: &[&str] = &["ch", "em", "ex", "ic", "lh", "rem", "rlh"];
12
13pub const UNITS_BASED_ON_VIEWPORT: &[&str] = &[
14 "vh", "svh", "lvh", "dvh", "vw", "svw", "lvw", "dvw", "vmin", "svmin", "lvmin", "dvmin", "vmax",
15 "svmax", "lvmax", "dvmax",
16];
17
18pub const UNITS_BASED_ON_CONTAINER: &[&str] = &["cqw", "cqi", "cqh", "cqb", "cqmin", "cqmax"];
20
21pub const UNITS_BASED_ON_ABSOLUTE_UNITS: &[&str] = &["px", "cm", "mm", "in", "pt"];
23
24#[derive(Debug, Clone, PartialEq)]
26pub struct Length {
27 pub value: f32,
28 pub unit: String,
29}
30
31impl Length {
32 pub fn new(value: f32, unit: String) -> Self {
34 Self { value, unit }
35 }
36
37 pub fn units() -> Vec<&'static str> {
39 let mut units = Vec::new();
40 units.extend_from_slice(UNITS_BASED_ON_FONT);
41 units.extend_from_slice(UNITS_BASED_ON_VIEWPORT);
42 units.extend_from_slice(UNITS_BASED_ON_CONTAINER);
43 units.extend_from_slice(UNITS_BASED_ON_ABSOLUTE_UNITS);
44 units
45 }
46
47 pub fn is_valid_unit(unit: &str) -> bool {
49 Self::units().contains(&unit)
50 }
51
52 pub fn parser() -> TokenParser<Length> {
54 let dimension_parser = TokenParser::<SimpleToken>::token(
56 SimpleToken::Dimension {
57 value: 0.0,
58 unit: String::new(),
59 },
60 Some("Dimension"),
61 )
62 .map(
63 |token| {
64 if let SimpleToken::Dimension { value, unit } = token {
65 Some((value as f32, unit))
66 } else {
67 None
68 }
69 },
70 Some("extract_dimension"),
71 )
72 .where_fn(
73 |opt| {
74 if let Some((_, unit)) = opt {
75 Self::is_valid_unit(unit)
76 } else {
77 false
78 }
79 },
80 Some("valid_length_unit"),
81 )
82 .map(
83 |opt| {
84 let (value, unit) = opt.unwrap();
85 Length::new(value, unit)
86 },
87 Some("to_length"),
88 );
89
90 let zero_parser = TokenParser::<SimpleToken>::token(SimpleToken::Number(0.0), Some("Number"))
92 .where_fn(
93 |token| {
94 if let SimpleToken::Number(value) = token {
95 *value == 0.0
96 } else {
97 false
98 }
99 },
100 Some("zero_value"),
101 )
102 .map(|_| Length::new(0.0, String::new()), Some("zero_length"));
103
104 TokenParser::one_of(vec![dimension_parser, zero_parser])
106 }
107}
108
109impl Display for Length {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{}{}", self.value, self.unit)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_length_creation() {
121 let len = Length::new(16.0, "px".to_string());
122 assert_eq!(len.value, 16.0);
123 assert_eq!(len.unit, "px");
124 }
125
126 #[test]
127 fn test_length_display() {
128 let len = Length::new(16.0, "px".to_string());
129 assert_eq!(len.to_string(), "16px");
130
131 let zero_len = Length::new(0.0, String::new());
132 assert_eq!(zero_len.to_string(), "0");
133 }
134
135 #[test]
136 fn test_valid_units() {
137 assert!(Length::is_valid_unit("em"));
139 assert!(Length::is_valid_unit("rem"));
140 assert!(Length::is_valid_unit("ch"));
141
142 assert!(Length::is_valid_unit("vh"));
143 assert!(Length::is_valid_unit("vw"));
144 assert!(Length::is_valid_unit("vmin"));
145
146 assert!(Length::is_valid_unit("cqw"));
148 assert!(Length::is_valid_unit("cqh"));
149
150 assert!(Length::is_valid_unit("px"));
152 assert!(Length::is_valid_unit("cm"));
153 assert!(Length::is_valid_unit("in"));
154
155 assert!(!Length::is_valid_unit("invalid"));
157 assert!(!Length::is_valid_unit("deg"));
158 assert!(!Length::is_valid_unit("s"));
159 }
160
161 #[test]
162 fn test_units_constants() {
163 assert!(UNITS_BASED_ON_FONT.contains(&"em"));
164 assert!(UNITS_BASED_ON_FONT.contains(&"rem"));
165
166 assert!(UNITS_BASED_ON_VIEWPORT.contains(&"vh"));
167 assert!(UNITS_BASED_ON_VIEWPORT.contains(&"vw"));
168
169 assert!(UNITS_BASED_ON_CONTAINER.contains(&"cqw"));
170
171 assert!(UNITS_BASED_ON_ABSOLUTE_UNITS.contains(&"px"));
172 assert!(UNITS_BASED_ON_ABSOLUTE_UNITS.contains(&"cm"));
173 }
174
175 #[test]
176 fn test_length_parser_creation() {
177 let _parser = Length::parser();
179 }
180
181 #[test]
182 fn test_all_units_included() {
183 let all_units = Length::units();
184
185 assert!(all_units.len() > 20); assert!(all_units.contains(&"px"));
188 assert!(all_units.contains(&"em"));
189 assert!(all_units.contains(&"vh"));
190 assert!(all_units.contains(&"cqw"));
191 }
192}