1use css_parse::{Build, Cursor, Parser, Peek, T, ToNumberValue};
2use csskit_derives::{IntoCursor, Peek, ToCursors, Visitable};
3
4use super::Flex;
5
6macro_rules! apply_lengths {
7 ($ident: ident) => {
8 $ident! {
9 Em,
11 Rem,
12 Ex,
13 Rex,
14 Cap,
15 Rcap,
16 Ch,
17 Rch,
18 Ic,
19 Ric,
20 Lh,
21 Rlh,
22
23 Vw,
25 Svw,
26 Lvw,
27 Dvw,
28 Vh,
29 Svh,
30 Lvh,
31 Dvh,
32 Vi,
33 Svi,
34 Lvi,
35 Dvi,
36 Vb,
37 Svb,
38 Lvb,
39 Dvb,
40 Vmin,
41 Svmin,
42 Lvmin,
43 Dvmin,
44 Vmax,
45 Svmax,
46 Lvmax,
47 Dvmax,
48
49 Cm,
51 Mm,
52 Q,
53 In,
54 Pc,
55 Pt,
56 Px,
57
58 Cqw,
60 Cqh,
61 Cqi,
62 Cqb,
63 Cqmin,
64 Cqmax,
65 }
66 };
67}
68
69macro_rules! define_length {
70 ( $($name: ident),+ $(,)* ) => {
71 #[derive(ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
72 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(tag = "type", content = "value", rename_all = "kebab-case"))]
73 #[visit(self)]
74 pub enum Length {
75 Zero(T![Number]),
76 $($name(T![Dimension::$name]),)+
77 }
78 }
79}
80apply_lengths!(define_length);
81
82impl Length {
83 const PX_CM: f32 = Self::PX_IN / 2.54;
84 const PX_MM: f32 = Self::PX_IN / 25.4;
85 const PX_Q: f32 = Self::PX_MM / 4.0;
86 const PX_IN: f32 = 96.0;
87 const PX_PC: f32 = Self::PX_IN / 6.0;
88 const PX_PT: f32 = Self::PX_IN / 72.0;
89
90 pub fn to_px(&self) -> Option<f32> {
91 match self {
92 Self::Zero(_) => Some(0.0),
93 Self::Cm(d) => Some(Into::<f32>::into(*d) * Self::PX_CM),
94 Self::Mm(d) => Some(Into::<f32>::into(*d) * Self::PX_MM),
95 Self::Q(d) => Some(Into::<f32>::into(*d) * Self::PX_Q),
96 Self::In(d) => Some(Into::<f32>::into(*d) * Self::PX_IN),
97 Self::Pc(d) => Some(Into::<f32>::into(*d) * Self::PX_PC),
98 Self::Pt(d) => Some(Into::<f32>::into(*d) * Self::PX_PT),
99 _ => None,
100 }
101 }
102}
103
104impl From<Length> for f32 {
105 fn from(val: Length) -> Self {
106 macro_rules! match_length {
107 ( $($name: ident),+ $(,)* ) => {
108 match val {
109 Length::Zero(_) => 0.0,
110 $(Length::$name(f) => f.into()),+
111 }
112 }
113 }
114 apply_lengths!(match_length)
115 }
116}
117
118impl PartialEq<f32> for Length {
119 fn eq(&self, other: &f32) -> bool {
120 let f: f32 = (*self).into();
121 f == *other
122 }
123}
124
125impl ToNumberValue for Length {
126 fn to_number_value(&self) -> Option<f32> {
127 Some((*self).into())
128 }
129}
130
131impl<'a> Peek<'a> for Length {
132 fn peek(p: &Parser<'a>, c: Cursor) -> bool {
133 macro_rules! is_checks {
134 ( $($name: ident),+ $(,)* ) => {
135 (<T![Number]>::peek(p, c) && c.token().value() == 0.0)
136 $(|| <T![Dimension::$name]>::peek(p, c))+
137 }
138 }
139 apply_lengths!(is_checks)
140 }
141}
142
143impl<'a> Build<'a> for Length {
144 fn build(p: &Parser<'a>, c: Cursor) -> Self {
145 debug_assert!(Self::peek(p, c));
146 macro_rules! build_steps {
147 ( $($name: ident),+ $(,)* ) => {
148 $(if <T![Dimension::$name]>::peek(p, c) {
149 Self::$name(<T![Dimension::$name]>::build(p, c))
150 } else )+ {
151 Self::Zero(<T![Number]>::build(p, c))
152 }
153 }
154 }
155 apply_lengths!(build_steps)
156 }
157}
158
159macro_rules! define_length_percentage {
160 ( $($name: ident),+ $(,)* ) => {
161 #[derive(ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
162 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(tag = "type", content = "value", rename_all = "kebab-case"))]
163 #[visit(self)]
164 pub enum LengthPercentage {
165 Zero(T![Number]),
166 $($name(T![Dimension::$name]),)+
167 Percent(T![Dimension::%]),
168 }
169 }
170}
171apply_lengths!(define_length_percentage);
172
173impl From<LengthPercentage> for f32 {
174 fn from(val: LengthPercentage) -> Self {
175 macro_rules! match_length {
176 ( $($name: ident),+ $(,)* ) => {
177 match val {
178 LengthPercentage::Zero(_) => 0.0,
179 LengthPercentage::Percent(f) => f.into(),
180 $(LengthPercentage::$name(f) => f.into()),+
181 }
182 }
183 }
184 apply_lengths!(match_length)
185 }
186}
187
188impl ToNumberValue for LengthPercentage {
189 fn to_number_value(&self) -> Option<f32> {
190 Some((*self).into())
191 }
192}
193
194impl<'a> Peek<'a> for LengthPercentage {
195 fn peek(p: &Parser<'a>, c: Cursor) -> bool {
196 macro_rules! is_checks {
197 ( $($name: ident),+ $(,)* ) => {
198 (<T![Number]>::peek(p, c) && c.token().value() == 0.0)
199 || <T![Dimension::%]>::peek(p, c)
200 $(|| <T![Dimension::$name]>::peek(p, c))+
201 }
202 }
203 apply_lengths!(is_checks)
204 }
205}
206
207impl<'a> Build<'a> for LengthPercentage {
208 fn build(p: &Parser<'a>, c: Cursor) -> Self {
209 debug_assert!(Self::peek(p, c));
210 macro_rules! build_steps {
211 ( $($name: ident),+ $(,)* ) => {
212 $(if <T![Dimension::$name]>::peek(p, c) {
213 Self::$name(<T![Dimension::$name]>::build(p, c))
214 } else )+ if <T![Dimension::%]>::peek(p, c) {
215 Self::Percent(<T![Dimension::%]>::build(p, c))
216 } else {
217 Self::Zero(<T![Number]>::build(p, c))
218 }
219 }
220 }
221 apply_lengths!(build_steps)
222 }
223}
224
225#[derive(IntoCursor, Peek, ToCursors, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(rename_all = "kebab-case"))]
227#[visit(children)]
228pub enum LengthPercentageOrFlex {
229 Flex(Flex),
230 LengthPercentage(LengthPercentage),
231}
232
233impl<'a> Build<'a> for LengthPercentageOrFlex {
234 fn build(p: &Parser<'a>, c: Cursor) -> Self {
235 debug_assert!(Self::peek(p, c));
236 if Flex::peek(p, c) {
237 Self::Flex(Flex::build(p, c))
238 } else {
239 Self::LengthPercentage(LengthPercentage::build(p, c))
240 }
241 }
242}
243
244#[derive(Peek, ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
245#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
246#[visit]
247pub enum NumberLength {
248 #[visit(skip)]
249 Number(T![Number]),
250 Length(Length),
251}
252
253impl From<NumberLength> for f32 {
254 fn from(val: NumberLength) -> Self {
255 match val {
256 NumberLength::Number(n) => n.into(),
257 NumberLength::Length(n) => n.into(),
258 }
259 }
260}
261
262impl ToNumberValue for NumberLength {
263 fn to_number_value(&self) -> Option<f32> {
264 Some((*self).into())
265 }
266}
267
268impl<'a> Build<'a> for NumberLength {
269 fn build(p: &Parser<'a>, c: Cursor) -> Self {
270 debug_assert!(Self::peek(p, c));
271 if Length::peek(p, c) { Self::Length(Length::build(p, c)) } else { Self::Number(<T![Number]>::build(p, c)) }
272 }
273}
274
275#[derive(Peek, ToCursors, IntoCursor, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
276#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
277#[visit(self)]
278pub enum NumberPercentage {
279 Number(T![Number]),
280 Percentage(T![Dimension::%]),
281}
282
283impl From<NumberPercentage> for f32 {
284 fn from(val: NumberPercentage) -> Self {
285 match val {
286 NumberPercentage::Number(n) => n.into(),
287 NumberPercentage::Percentage(n) => n.into(),
288 }
289 }
290}
291
292impl ToNumberValue for NumberPercentage {
293 fn to_number_value(&self) -> Option<f32> {
294 Some((*self).into())
295 }
296}
297
298impl<'a> Build<'a> for NumberPercentage {
299 fn build(p: &Parser<'a>, c: Cursor) -> Self {
300 debug_assert!(Self::peek(p, c));
301 if <T![Number]>::peek(p, c) {
302 Self::Number(<T![Number]>::build(p, c))
303 } else {
304 Self::Percentage(<T![Dimension::%]>::build(p, c))
305 }
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use css_parse::assert_parse;
313
314 #[test]
315 fn size_test() {
316 assert_eq!(std::mem::size_of::<Length>(), 16);
317 assert_eq!(std::mem::size_of::<LengthPercentage>(), 16);
318 assert_eq!(std::mem::size_of::<NumberLength>(), 16);
319 assert_eq!(std::mem::size_of::<NumberPercentage>(), 16);
320 }
321
322 #[test]
323 fn test_writes() {
324 assert_parse!(Length, "10px");
325 assert_parse!(Length, "1.2345679px");
327 assert_parse!(Length, "-1px");
329 assert_parse!(LengthPercentage, "1%");
331 }
332}