Skip to main content

stylex_css_parser/css_types/
dimension.rs

1/*!
2CSS Dimension type parsing.
3
4Handles dimensional values that can be lengths, times, frequencies, or resolutions.
5*/
6
7use crate::{token_parser::TokenParser, token_types::SimpleToken};
8use std::fmt::{self, Display};
9
10use super::{frequency::Frequency, length::Length, resolution::Resolution, time::Time};
11
12/// Union type for all dimensional CSS values
13#[derive(Debug, Clone, PartialEq)]
14pub enum Dimension {
15  Length(Length),
16  Time(Time),
17  Frequency(Frequency),
18  Resolution(Resolution),
19}
20
21impl Dimension {
22  /// Check if a unit belongs to any dimension type
23  #[allow(dead_code)]
24  fn is_valid_dimension_unit(unit: &str) -> bool {
25    Length::is_valid_unit(unit)
26      || Time::is_valid_unit(unit)
27      || Frequency::is_valid_unit(unit)
28      || Resolution::is_valid_unit(unit)
29  }
30
31  /// Create a Dimension from value and unit
32  fn from_value_and_unit(value: f32, unit: String) -> Option<Dimension> {
33    if Length::is_valid_unit(&unit) {
34      Some(Dimension::Length(Length::new(value, unit)))
35    } else if Time::is_valid_unit(&unit) {
36      Some(Dimension::Time(Time::new(value, unit)))
37    } else if Frequency::is_valid_unit(&unit) {
38      Some(Dimension::Frequency(Frequency::new(value, unit)))
39    } else if Resolution::is_valid_unit(&unit) {
40      Some(Dimension::Resolution(Resolution::new(value, unit)))
41    } else {
42      None
43    }
44  }
45
46  /// Parser for dimensional values
47  pub fn parse() -> TokenParser<Dimension> {
48    use crate::token_parser::tokens;
49
50    tokens::dimension()
51      .map(
52        |token| {
53          if let SimpleToken::Dimension { value, unit } = token {
54            Self::from_value_and_unit(value as f32, unit)
55          } else {
56            None
57          }
58        },
59        Some("extract_dimension"),
60      )
61      .where_fn(|opt| opt.is_some(), Some("valid_dimension"))
62      .map(|opt| opt.unwrap(), Some("unwrap_dimension"))
63  }
64}
65
66pub fn dimension() -> TokenParser<Dimension> {
67  Dimension::parse()
68}
69
70impl Display for Dimension {
71  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72    match self {
73      Dimension::Length(length) => length.fmt(f),
74      Dimension::Time(time) => time.fmt(f),
75      Dimension::Frequency(frequency) => frequency.fmt(f),
76      Dimension::Resolution(resolution) => resolution.fmt(f),
77    }
78  }
79}
80
81#[cfg(test)]
82mod tests {
83  use super::*;
84
85  #[test]
86  fn test_dimension_from_value_and_unit() {
87    // Length
88    let length_dim = Dimension::from_value_and_unit(16.0, "px".to_string());
89    assert!(matches!(length_dim, Some(Dimension::Length(_))));
90
91    // Time
92    let time_dim = Dimension::from_value_and_unit(1.5, "s".to_string());
93    assert!(matches!(time_dim, Some(Dimension::Time(_))));
94
95    // Frequency
96    let freq_dim = Dimension::from_value_and_unit(440.0, "Hz".to_string());
97    assert!(matches!(freq_dim, Some(Dimension::Frequency(_))));
98
99    // Resolution
100    let res_dim = Dimension::from_value_and_unit(96.0, "dpi".to_string());
101    assert!(matches!(res_dim, Some(Dimension::Resolution(_))));
102
103    // Invalid unit
104    let invalid_dim = Dimension::from_value_and_unit(10.0, "invalid".to_string());
105    assert!(invalid_dim.is_none());
106  }
107
108  #[test]
109  fn test_dimension_display() {
110    let length = Dimension::Length(Length::new(16.0, "px".to_string()));
111    assert_eq!(length.to_string(), "16px");
112
113    let time = Dimension::Time(Time::new(1.5, "s".to_string()));
114    assert_eq!(time.to_string(), "1.5s");
115
116    let freq = Dimension::Frequency(Frequency::new(440.0, "Hz".to_string()));
117    assert_eq!(freq.to_string(), "0.44KHz");
118
119    let res = Dimension::Resolution(Resolution::new(96.0, "dpi".to_string()));
120    assert_eq!(res.to_string(), "96dpi");
121  }
122
123  #[test]
124  fn test_is_valid_dimension_unit() {
125    // Length units
126    assert!(Dimension::is_valid_dimension_unit("px"));
127    assert!(Dimension::is_valid_dimension_unit("em"));
128    assert!(Dimension::is_valid_dimension_unit("vh"));
129
130    // Time units
131    assert!(Dimension::is_valid_dimension_unit("s"));
132    assert!(Dimension::is_valid_dimension_unit("ms"));
133
134    // Frequency units
135    assert!(Dimension::is_valid_dimension_unit("Hz"));
136    assert!(Dimension::is_valid_dimension_unit("KHz"));
137
138    // Resolution units
139    assert!(Dimension::is_valid_dimension_unit("dpi"));
140    assert!(Dimension::is_valid_dimension_unit("dpcm"));
141    assert!(Dimension::is_valid_dimension_unit("dppx"));
142
143    // Invalid units
144    assert!(!Dimension::is_valid_dimension_unit("invalid"));
145    assert!(!Dimension::is_valid_dimension_unit("deg")); // This is an angle unit, handled by Angle type separately
146  }
147
148  #[test]
149  fn test_dimension_parser_creation() {
150    // Basic test that parser can be created
151    let _parser = Dimension::parse();
152  }
153
154  #[test]
155  fn test_dimension_equality() {
156    let dim1 = Dimension::Length(Length::new(16.0, "px".to_string()));
157    let dim2 = Dimension::Length(Length::new(16.0, "px".to_string()));
158    let dim3 = Dimension::Length(Length::new(20.0, "px".to_string()));
159
160    assert_eq!(dim1, dim2);
161    assert_ne!(dim1, dim3);
162  }
163}