grass_compiler/unit/
mod.rs

1use std::{fmt, sync::Arc};
2
3use crate::interner::InternedString;
4
5pub(crate) use conversion::{known_compatibilities_by_unit, UNIT_CONVERSION_TABLE};
6
7mod conversion;
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
10pub enum Unit {
11    // Absolute units
12    /// Pixels
13    Px,
14    /// Millimeters
15    Mm,
16    /// Inches
17    In,
18    /// Centimeters
19    Cm,
20    /// Quarter-millimeters
21    Q,
22    /// Points
23    Pt,
24    /// Picas
25    Pc,
26
27    // Font relative units
28    /// Font size of the parent element
29    Em,
30    /// Font size of the root element
31    Rem,
32    /// Line height of the element
33    Lh,
34    /// x-height of the element's font
35    Ex,
36    /// The advance measure (width) of the glyph "0" of the element's font
37    Ch,
38    /// Represents the "cap height" (nominal height of capital letters) of the element's font
39    Cap,
40    /// Equal to the used advance measure of the "水" (CJK water ideograph, U+6C34) glyph
41    /// found in the font used to render it
42    Ic,
43    /// Equal to the computed value of the line-height property on the root element
44    /// (typically \<html\>), converted to an absolute length
45    Rlh,
46
47    // Viewport relative units
48    /// 1% of the viewport's width
49    Vw,
50    /// 1% of the viewport's height
51    Vh,
52    /// 1% of the viewport's smaller dimension
53    Vmin,
54    /// 1% of the viewport's larger dimension
55    Vmax,
56    /// Equal to 1% of the size of the initial containing block, in the direction of the root
57    /// element's inline axis
58    Vi,
59    /// Equal to 1% of the size of the initial containing block, in the direction of the root
60    /// element's block axis
61    Vb,
62
63    // Angle units
64    /// Represents an angle in degrees. One full circle is 360deg
65    Deg,
66    /// Represents an angle in gradians. One full circle is 400grad
67    Grad,
68    /// Represents an angle in radians. One full circle is 2π radians which approximates to 6.283rad
69    Rad,
70    /// Represents an angle in a number of turns. One full circle is 1turn
71    Turn,
72
73    // Time units
74    /// Represents a time in seconds
75    S,
76    /// Represents a time in milliseconds
77    Ms,
78
79    // Frequency units
80    /// Represents a frequency in hertz
81    Hz,
82    /// Represents a frequency in kilohertz
83    Khz,
84
85    // Resolution units
86    /// Represents the number of dots per inch
87    Dpi,
88    /// Represents the number of dots per centimeter
89    Dpcm,
90    /// Represents the number of dots per px unit
91    Dppx,
92
93    // Other units
94    /// Represents a fraction of the available space in the grid container
95    Fr,
96    Percent,
97
98    /// Unknown unit
99    Unknown(InternedString),
100    /// Unspecified unit
101    None,
102
103    Complex(Arc<ComplexUnit>),
104}
105
106#[derive(Clone, Debug, PartialEq, Eq, Hash)]
107pub struct ComplexUnit {
108    pub numer: Vec<Unit>,
109    pub denom: Vec<Unit>,
110}
111
112pub(crate) fn are_any_convertible(units1: &[Unit], units2: &[Unit]) -> bool {
113    for unit1 in units1 {
114        for unit2 in units2 {
115            if unit1.comparable(unit2) {
116                return true;
117            }
118        }
119    }
120
121    false
122}
123
124#[derive(Debug, Copy, Clone, Eq, PartialEq)]
125pub(crate) enum UnitKind {
126    Absolute,
127    FontRelative,
128    ViewportRelative,
129    Angle,
130    Time,
131    Frequency,
132    Resolution,
133    Other,
134    None,
135}
136
137impl Unit {
138    pub(crate) fn new(mut numer: Vec<Self>, denom: Vec<Self>) -> Self {
139        if denom.is_empty() && numer.is_empty() {
140            Unit::None
141        } else if denom.is_empty() && numer.len() == 1 {
142            numer.pop().unwrap()
143        } else {
144            Unit::Complex(Arc::new(ComplexUnit { numer, denom }))
145        }
146    }
147
148    pub(crate) fn numer_and_denom(self) -> (Vec<Unit>, Vec<Unit>) {
149        match self {
150            Self::Complex(complex) => (complex.numer.clone(), complex.denom.clone()),
151            Self::None => (Vec::new(), Vec::new()),
152            v => (vec![v], Vec::new()),
153        }
154    }
155
156    pub(crate) fn invert(self) -> Self {
157        let (numer, denom) = self.numer_and_denom();
158
159        Self::new(denom, numer)
160    }
161
162    pub(crate) fn is_complex(&self) -> bool {
163        matches!(self, Unit::Complex(complex) if complex.numer.len() != 1 || !complex.denom.is_empty())
164    }
165
166    pub(crate) fn comparable(&self, other: &Unit) -> bool {
167        if other == &Unit::None {
168            return true;
169        }
170        match self.kind() {
171            UnitKind::FontRelative | UnitKind::ViewportRelative | UnitKind::Other => self == other,
172            UnitKind::None => true,
173            u => other.kind() == u,
174        }
175    }
176
177    /// Used internally to determine if two units are comparable or not
178    fn kind(&self) -> UnitKind {
179        match self {
180            Unit::Px | Unit::Mm | Unit::In | Unit::Cm | Unit::Q | Unit::Pt | Unit::Pc => {
181                UnitKind::Absolute
182            }
183            Unit::Em
184            | Unit::Rem
185            | Unit::Lh
186            | Unit::Ex
187            | Unit::Ch
188            | Unit::Cap
189            | Unit::Ic
190            | Unit::Rlh => UnitKind::FontRelative,
191            Unit::Vw | Unit::Vh | Unit::Vmin | Unit::Vmax | Unit::Vi | Unit::Vb => {
192                UnitKind::ViewportRelative
193            }
194            Unit::Deg | Unit::Grad | Unit::Rad | Unit::Turn => UnitKind::Angle,
195            Unit::S | Unit::Ms => UnitKind::Time,
196            Unit::Hz | Unit::Khz => UnitKind::Frequency,
197            Unit::Dpi | Unit::Dpcm | Unit::Dppx => UnitKind::Resolution,
198            Unit::None => UnitKind::None,
199            Unit::Fr | Unit::Percent | Unit::Unknown(..) | Unit::Complex { .. } => UnitKind::Other,
200        }
201    }
202}
203
204impl From<String> for Unit {
205    fn from(unit: String) -> Self {
206        match unit.to_ascii_lowercase().as_str() {
207            "px" => Unit::Px,
208            "mm" => Unit::Mm,
209            "in" => Unit::In,
210            "cm" => Unit::Cm,
211            "q" => Unit::Q,
212            "pt" => Unit::Pt,
213            "pc" => Unit::Pc,
214            "em" => Unit::Em,
215            "rem" => Unit::Rem,
216            "lh" => Unit::Lh,
217            "%" => Unit::Percent,
218            "ex" => Unit::Ex,
219            "ch" => Unit::Ch,
220            "cap" => Unit::Cap,
221            "ic" => Unit::Ic,
222            "rlh" => Unit::Rlh,
223            "vw" => Unit::Vw,
224            "vh" => Unit::Vh,
225            "vmin" => Unit::Vmin,
226            "vmax" => Unit::Vmax,
227            "vi" => Unit::Vi,
228            "vb" => Unit::Vb,
229            "deg" => Unit::Deg,
230            "grad" => Unit::Grad,
231            "rad" => Unit::Rad,
232            "turn" => Unit::Turn,
233            "s" => Unit::S,
234            "ms" => Unit::Ms,
235            "hz" => Unit::Hz,
236            "khz" => Unit::Khz,
237            "dpi" => Unit::Dpi,
238            "dpcm" => Unit::Dpcm,
239            "dppx" => Unit::Dppx,
240            "fr" => Unit::Fr,
241            _ => Unit::Unknown(InternedString::get_or_intern(unit)),
242        }
243    }
244}
245
246impl fmt::Display for Unit {
247    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
248        match self {
249            Unit::Px => write!(f, "px"),
250            Unit::Mm => write!(f, "mm"),
251            Unit::In => write!(f, "in"),
252            Unit::Cm => write!(f, "cm"),
253            Unit::Q => write!(f, "q"),
254            Unit::Pt => write!(f, "pt"),
255            Unit::Pc => write!(f, "pc"),
256            Unit::Em => write!(f, "em"),
257            Unit::Rem => write!(f, "rem"),
258            Unit::Lh => write!(f, "lh"),
259            Unit::Percent => write!(f, "%"),
260            Unit::Ex => write!(f, "ex"),
261            Unit::Ch => write!(f, "ch"),
262            Unit::Cap => write!(f, "cap"),
263            Unit::Ic => write!(f, "ic"),
264            Unit::Rlh => write!(f, "rlh"),
265            Unit::Vw => write!(f, "vw"),
266            Unit::Vh => write!(f, "vh"),
267            Unit::Vmin => write!(f, "vmin"),
268            Unit::Vmax => write!(f, "vmax"),
269            Unit::Vi => write!(f, "vi"),
270            Unit::Vb => write!(f, "vb"),
271            Unit::Deg => write!(f, "deg"),
272            Unit::Grad => write!(f, "grad"),
273            Unit::Rad => write!(f, "rad"),
274            Unit::Turn => write!(f, "turn"),
275            Unit::S => write!(f, "s"),
276            Unit::Ms => write!(f, "ms"),
277            Unit::Hz => write!(f, "Hz"),
278            Unit::Khz => write!(f, "kHz"),
279            Unit::Dpi => write!(f, "dpi"),
280            Unit::Dpcm => write!(f, "dpcm"),
281            Unit::Dppx => write!(f, "dppx"),
282            Unit::Fr => write!(f, "fr"),
283            Unit::Unknown(s) => write!(f, "{}", s),
284            Unit::None => Ok(()),
285            Unit::Complex(complex) => {
286                let numer = &complex.numer;
287                let denom = &complex.denom;
288                debug_assert!(
289                    numer.len() > 1 || !denom.is_empty(),
290                    "unsimplified complex unit"
291                );
292
293                let numer_rendered = numer
294                    .iter()
295                    .map(ToString::to_string)
296                    .collect::<Vec<String>>()
297                    .join("*");
298
299                let denom_rendered = denom
300                    .iter()
301                    .map(ToString::to_string)
302                    .collect::<Vec<String>>()
303                    .join("*");
304
305                if denom.is_empty() {
306                    write!(f, "{}", numer_rendered)
307                } else if numer.is_empty() && denom.len() == 1 {
308                    write!(f, "{}^-1", denom_rendered)
309                } else if numer.is_empty() {
310                    write!(f, "({})^-1", denom_rendered)
311                } else {
312                    write!(f, "{}/{}", numer_rendered, denom_rendered)
313                }
314            }
315        }
316    }
317}