lewp_css/domain/units/
length_unit.rs

1// This file is part of css. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
2// Copyright © 2017 The developers of css. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT.
3
4use {
5    super::{
6        conversions::{
7            FontRelativeLengthConversion,
8            PercentageConversion,
9            ViewportPercentageLengthConversion,
10        },
11        AbsoluteLength::{self, *},
12        FontRelativeLength::{self, *},
13        LengthUnit::*,
14        PercentageUnit,
15        Unit,
16        ViewportPercentageLength::{self, *},
17    },
18    crate::{
19        domain::{
20            expressions::{
21                CalcExpression,
22                CalculablePropertyValue::{self, *},
23                FunctionParser,
24            },
25            numbers::{CssNumber, CssNumberNewType},
26        },
27        parsers::ParserContext,
28        CustomParseError::{self, *},
29    },
30    cssparser::{CowRcStr, ParseError, Parser, ParserInput, ToCss, Token},
31    either::{Either, Left},
32    std::{fmt, ops::*},
33};
34
35/// A `<length>` without taking `calc` expressions into account
36///
37/// <https://drafts.csswg.org/css-values/#lengths>
38#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
39pub enum LengthUnit<Number: CssNumber> {
40    /// An absolute length
41    ///
42    /// <https://drafts.csswg.org/css-values/#absolute-length>
43    Absolute(AbsoluteLength<Number>),
44
45    /// A font-relative length:
46    ///
47    /// <https://drafts.csswg.org/css-values/#font-relative-lengths>
48    FontRelative(FontRelativeLength<Number>),
49
50    /// A viewport-relative length.
51    /// Not valid in @page rules (the parser enforces this)
52    ///
53    /// <https://drafts.csswg.org/css-values/#viewport-relative-lengths>
54    ViewportPercentage(ViewportPercentageLength<Number>),
55}
56
57impl<Number: CssNumber> ToCss for LengthUnit<Number> {
58    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
59        match *self {
60            Absolute(ref length) => length.to_css(dest),
61            FontRelative(ref length) => length.to_css(dest),
62            ViewportPercentage(ref length) => length.to_css(dest),
63        }
64    }
65}
66
67impl<Number: CssNumber> Default for LengthUnit<Number> {
68    #[inline(always)]
69    fn default() -> Self {
70        Absolute(AbsoluteLength::default())
71    }
72}
73
74impl<Number: CssNumber> Add<Number> for LengthUnit<Number> {
75    type Output = Self;
76
77    #[inline(always)]
78    fn add(self, rhs: Number) -> Self::Output {
79        match self {
80            Absolute(length) => Absolute(length + rhs),
81            FontRelative(length) => FontRelative(length + rhs),
82            ViewportPercentage(length) => ViewportPercentage(length + rhs),
83        }
84    }
85}
86
87impl<Number: CssNumber> AddAssign<Number> for LengthUnit<Number> {
88    #[inline(always)]
89    fn add_assign(&mut self, rhs: Number) {
90        match *self {
91            Absolute(ref mut length) => *length = *length + rhs,
92            FontRelative(ref mut length) => *length = *length + rhs,
93            ViewportPercentage(ref mut length) => *length = *length + rhs,
94        }
95    }
96}
97
98impl<Number: CssNumber> Sub<Number> for LengthUnit<Number> {
99    type Output = Self;
100
101    #[inline(always)]
102    fn sub(self, rhs: Number) -> Self::Output {
103        match self {
104            Absolute(length) => Absolute(length - rhs),
105            FontRelative(length) => FontRelative(length - rhs),
106            ViewportPercentage(length) => ViewportPercentage(length - rhs),
107        }
108    }
109}
110
111impl<Number: CssNumber> SubAssign<Number> for LengthUnit<Number> {
112    #[inline(always)]
113    fn sub_assign(&mut self, rhs: Number) {
114        match *self {
115            Absolute(ref mut length) => *length = *length - rhs,
116            FontRelative(ref mut length) => *length = *length - rhs,
117            ViewportPercentage(ref mut length) => *length = *length - rhs,
118        }
119    }
120}
121
122impl<Number: CssNumber> Mul<Number> for LengthUnit<Number> {
123    type Output = Self;
124
125    #[inline(always)]
126    fn mul(self, rhs: Number) -> Self::Output {
127        match self {
128            Absolute(length) => Absolute(length * rhs),
129            FontRelative(length) => FontRelative(length * rhs),
130            ViewportPercentage(length) => ViewportPercentage(length * rhs),
131        }
132    }
133}
134
135impl<Number: CssNumber> MulAssign<Number> for LengthUnit<Number> {
136    #[inline(always)]
137    fn mul_assign(&mut self, rhs: Number) {
138        match *self {
139            Absolute(ref mut length) => *length = *length * rhs,
140            FontRelative(ref mut length) => *length = *length * rhs,
141            ViewportPercentage(ref mut length) => *length = *length * rhs,
142        }
143    }
144}
145
146impl<Number: CssNumber> Div<Number> for LengthUnit<Number> {
147    type Output = Self;
148
149    #[inline(always)]
150    fn div(self, rhs: Number) -> Self::Output {
151        match self {
152            Absolute(length) => Absolute(length / rhs),
153            FontRelative(length) => FontRelative(length / rhs),
154            ViewportPercentage(length) => ViewportPercentage(length / rhs),
155        }
156    }
157}
158
159impl<Number: CssNumber> DivAssign<Number> for LengthUnit<Number> {
160    #[inline(always)]
161    fn div_assign(&mut self, rhs: Number) {
162        match *self {
163            Absolute(ref mut length) => *length = *length / rhs,
164            FontRelative(ref mut length) => *length = *length / rhs,
165            ViewportPercentage(ref mut length) => *length = *length / rhs,
166        }
167    }
168}
169
170impl<Number: CssNumber> Rem<Number> for LengthUnit<Number> {
171    type Output = LengthUnit<Number>;
172
173    #[inline(always)]
174    fn rem(self, rhs: Number) -> Self::Output {
175        match self {
176            Absolute(length) => Absolute(length % rhs),
177            FontRelative(length) => FontRelative(length % rhs),
178            ViewportPercentage(length) => ViewportPercentage(length % rhs),
179        }
180    }
181}
182
183impl<Number: CssNumber> RemAssign<Number> for LengthUnit<Number> {
184    #[inline(always)]
185    fn rem_assign(&mut self, rhs: Number) {
186        match *self {
187            Absolute(ref mut length) => *length = *length % rhs,
188            FontRelative(ref mut length) => *length = *length % rhs,
189            ViewportPercentage(ref mut length) => *length = *length % rhs,
190        }
191    }
192}
193
194impl<Number: CssNumber> Neg for LengthUnit<Number> {
195    type Output = LengthUnit<Number>;
196
197    #[inline(always)]
198    fn neg(self) -> Self::Output {
199        match self {
200            Absolute(length) => Absolute(-length),
201            FontRelative(length) => FontRelative(-length),
202            ViewportPercentage(length) => ViewportPercentage(-length),
203        }
204    }
205}
206
207impl<Number: CssNumber> CssNumberNewType<Number> for LengthUnit<Number> {
208    #[inline(always)]
209    fn to_f32(&self) -> f32 {
210        self.to_CssNumber().to_f32()
211    }
212
213    #[inline(always)]
214    fn as_CssNumber(&self) -> &Number {
215        match *self {
216            Absolute(ref length) => length.as_CssNumber(),
217            FontRelative(ref length) => length.as_CssNumber(),
218            ViewportPercentage(ref length) => length.as_CssNumber(),
219        }
220    }
221}
222
223impl<NumberX: CssNumber> Unit for LengthUnit<NumberX> {
224    type Number = NumberX;
225
226    const HasDimension: bool = true;
227
228    #[inline(always)]
229    fn parse_one_outside_calc_function<'i, 't>(
230        context: &ParserContext,
231        input: &mut Parser<'i, 't>,
232    ) -> Result<
233        CalculablePropertyValue<Self>,
234        ParseError<'i, CustomParseError<'i>>,
235    > {
236        let functionParser = match *input.next()? {
237            Token::Number { value, .. } => {
238                return Self::parseUnitLessNumber(
239                    value,
240                    context.parsing_mode_allows_unitless_lengths(),
241                )
242                .map(Constant)
243            }
244
245            Token::Dimension {
246                value, ref unit, ..
247            } => {
248                return Self::parseDimension(
249                    value,
250                    unit,
251                    context.isNotInPageRule(),
252                )
253                .map(Constant)
254            }
255
256            Token::Function(ref name) => FunctionParser::parser(name)?,
257
258            ref unexpectedToken => {
259                return CustomParseError::unexpectedToken(unexpectedToken)
260            }
261        };
262        functionParser.parse_one_outside_calc_function(context, input)
263    }
264
265    #[inline(always)]
266    fn parse_one_inside_calc_function<'i, 't>(
267        context: &ParserContext,
268        input: &mut Parser<'i, 't>,
269    ) -> Result<
270        Either<CalculablePropertyValue<Self>, CalcExpression<Self>>,
271        ParseError<'i, CustomParseError<'i>>,
272    > {
273        let functionParser = match *input.next()? {
274            Token::Number { value, .. } => {
275                return Self::number_inside_calc_function(value)
276            }
277
278            Token::Percentage { unit_value, .. } => {
279                return PercentageUnit::parse_percentage(unit_value)
280                    .map(|value| Left(Percentage(value)))
281            }
282
283            Token::Dimension {
284                value, ref unit, ..
285            } => {
286                return Self::parseDimension(
287                    value,
288                    unit,
289                    context.isNotInPageRule(),
290                )
291                .map(|value| Left(Constant(value)))
292            }
293
294            Token::ParenthesisBlock => FunctionParser::parentheses,
295
296            Token::Function(ref name) => FunctionParser::parser(name)?,
297
298            ref unexpectedToken => {
299                return CustomParseError::unexpectedToken(unexpectedToken)
300            }
301        };
302        functionParser.parse_one_inside_calc_function(context, input)
303    }
304
305    #[inline(always)]
306    fn to_canonical_dimension(self) -> Self {
307        match self {
308            Absolute(ref length) => Absolute(px(length.to_px())),
309            unchanged => unchanged,
310        }
311    }
312
313    #[inline(always)]
314    fn to_canonical_dimension_value<
315        Conversion: FontRelativeLengthConversion<Self::Number>
316            + ViewportPercentageLengthConversion<Self::Number>
317            + PercentageConversion<Self::Number>,
318    >(
319        &self,
320        conversion: &Conversion,
321    ) -> Self::Number {
322        self.to_px(conversion)
323    }
324
325    #[inline(always)]
326    fn from_raw_css_for_var_expression_evaluation(
327        value: &str,
328        is_not_in_page_rule: bool,
329    ) -> Option<Self> {
330        fn from_raw_css_for_var_expression_evaluation_internal<
331            'i: 't,
332            't,
333            Number: CssNumber,
334        >(
335            is_not_in_page_rule: bool,
336            input: &mut Parser<'i, 't>,
337        ) -> Result<LengthUnit<Number>, ParseError<'i, CustomParseError<'i>>>
338        {
339            let value = match *input.next()? {
340                Token::Number { value, .. } => {
341                    LengthUnit::parseUnitLessNumber(value, false)
342                }
343
344                Token::Dimension {
345                    value, ref unit, ..
346                } => {
347                    LengthUnit::parseDimension(value, unit, is_not_in_page_rule)
348                }
349
350                ref unexpectedToken => {
351                    CustomParseError::unexpectedToken(unexpectedToken)
352                }
353            };
354
355            input.skip_whitespace();
356
357            input.expect_exhausted()?;
358
359            value
360        }
361
362        const LineNumberingIsZeroBased: u32 = 0;
363
364        let mut parserInput = ParserInput::new_with_line_number_offset(
365            value,
366            LineNumberingIsZeroBased,
367        );
368        let mut input = Parser::new(&mut parserInput);
369
370        from_raw_css_for_var_expression_evaluation_internal(
371            is_not_in_page_rule,
372            &mut input,
373        )
374        .ok()
375    }
376}
377
378impl<Number: CssNumber> LengthUnit<Number> {
379    /// Checks whether the length value is zero.
380    #[inline]
381    pub fn is_absolute_zero(&self) -> bool {
382        match *self {
383            Absolute(length) => length.is_zero(),
384            _ => false,
385        }
386    }
387
388    #[inline(always)]
389    pub fn is_absolute_length(&self) -> bool {
390        match *self {
391            Absolute(..) => true,
392            FontRelative(..) | ViewportPercentage(..) => false,
393        }
394    }
395
396    /// Convert this into a pixel value.
397    #[inline(always)]
398    pub fn to_px<
399        Conversion: FontRelativeLengthConversion<Number>
400            + ViewportPercentageLengthConversion<Number>,
401    >(
402        &self,
403        conversion: &Conversion,
404    ) -> Number {
405        match *self {
406            Absolute(ref length) => length.to_px(),
407            FontRelative(ref length) => length.to_px(conversion),
408            ViewportPercentage(ref length) => length.to_px(conversion),
409        }
410    }
411
412    /// Convert this into AppUnits.
413    #[inline]
414    pub fn to_app_units<
415        Conversion: FontRelativeLengthConversion<Number>
416            + ViewportPercentageLengthConversion<Number>,
417    >(
418        &self,
419        conversion: &Conversion,
420    ) -> Number {
421        match *self {
422            Absolute(ref length) => length.to_app_units(),
423            FontRelative(ref length) => length.to_app_units(conversion),
424            ViewportPercentage(ref length) => length.to_app_units(conversion),
425        }
426    }
427
428    #[inline(always)]
429    pub(crate) fn parseUnitLessNumber<'i>(
430        value: f32,
431        parsing_mode_allows_unitless_lengths: bool,
432    ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
433        if value == 0. {
434            Ok(Self::default())
435        } else if parsing_mode_allows_unitless_lengths {
436            let cssNumber =
437                Number::new(value).map_err(|cssNumberConversionError| {
438                    ParseError::from(CouldNotParseCssSignedNumber(
439                        cssNumberConversionError,
440                        value,
441                    ))
442                })?;
443            Ok(Absolute(px(cssNumber)))
444        } else {
445            CustomParseError::dimensionless(value)
446        }
447    }
448
449    #[inline(always)]
450    pub(crate) fn parseDimension<'i>(
451        value: f32,
452        unit: &CowRcStr<'i>,
453        is_not_in_page_rule: bool,
454    ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
455        let cssNumber =
456            Number::new(value).map_err(|cssNumberConversionError| {
457                ParseError::from(CouldNotParseCssSignedNumber(
458                    cssNumberConversionError,
459                    value,
460                ))
461            })?;
462
463        match_ignore_ascii_case! {
464            &*unit,
465
466            "px" => Ok(Absolute(px(cssNumber))),
467
468            "in" => Ok(Absolute(in_(cssNumber))),
469
470            "cm" => Ok(Absolute(cm(cssNumber))),
471
472            "mm" => Ok(Absolute(mm(cssNumber))),
473
474            "q" => Ok(Absolute(q(cssNumber))),
475
476            "pt" => Ok(Absolute(pt(cssNumber))),
477
478            "pc" => Ok(Absolute(pc(cssNumber))),
479
480            "em" => if is_not_in_page_rule
481            {
482                Ok(FontRelative(em(cssNumber)))
483            }
484            else
485            {
486                Err(ParseError::from(CustomParseError::FontRelativeLengthsAreNotAllowedInAPageAtRule))
487            },
488
489            "ex" => if is_not_in_page_rule
490            {
491                Ok(FontRelative(ex(cssNumber)))
492            }
493            else
494            {
495                Err(ParseError::from(CustomParseError::FontRelativeLengthsAreNotAllowedInAPageAtRule))
496            },
497
498            "ch" => Ok(FontRelative(ch(cssNumber))),
499
500            "rem" => Ok(FontRelative(rem(cssNumber))),
501
502            "vw" => if is_not_in_page_rule
503            {
504                Ok(ViewportPercentage(vw(cssNumber)))
505            }
506            else
507            {
508                Err(ParseError::from(CustomParseError::ViewportLengthsAreNotAllowedInAPageAtRule))
509            },
510
511            "vh" => if is_not_in_page_rule
512            {
513                Ok(ViewportPercentage(vh(cssNumber)))
514            }
515            else
516            {
517                Err(ParseError::from(CustomParseError::ViewportLengthsAreNotAllowedInAPageAtRule))
518            },
519
520            "vmin" => if is_not_in_page_rule
521            {
522                Ok(ViewportPercentage(vmin(cssNumber)))
523            }
524            else
525            {
526                Err(ParseError::from(CustomParseError::ViewportLengthsAreNotAllowedInAPageAtRule))
527            },
528
529            "vmax" => if is_not_in_page_rule
530            {
531                Ok(ViewportPercentage(vmax(cssNumber)))
532            }
533            else
534            {
535                Err(ParseError::from(CustomParseError::ViewportLengthsAreNotAllowedInAPageAtRule))
536            },
537
538            _ => Err(ParseError::from(CouldNotParseDimension(value, unit.clone()))),
539        }
540    }
541}