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 Px,
14 Mm,
16 In,
18 Cm,
20 Q,
22 Pt,
24 Pc,
26
27 Em,
30 Rem,
32 Lh,
34 Ex,
36 Ch,
38 Cap,
40 Ic,
43 Rlh,
46
47 Vw,
50 Vh,
52 Vmin,
54 Vmax,
56 Vi,
59 Vb,
62
63 Deg,
66 Grad,
68 Rad,
70 Turn,
72
73 S,
76 Ms,
78
79 Hz,
82 Khz,
84
85 Dpi,
88 Dpcm,
90 Dppx,
92
93 Fr,
96 Percent,
97
98 Unknown(InternedString),
100 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 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}