style/values/specified/
calc.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! [Calc expressions][calc].
6//!
7//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
8
9use crate::color::parsing::ChannelKeyword;
10use crate::parser::{ParserContext, Parse};
11use crate::values::generics::position::{GenericAnchorSide, AnchorSideKeyword, GenericAnchorFunction};
12use crate::values::generics::length::GenericAnchorSizeFunction;
13use crate::values::generics::calc::{
14    self as generic, CalcNodeLeaf, CalcUnits, MinMaxOp, ModRemOp, PositivePercentageBasis,
15    RoundingStrategy, SortKey,
16};
17use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength};
18use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength};
19use crate::values::specified::{self, Angle, Resolution, Time};
20use crate::values::{serialize_number, serialize_percentage, CSSFloat, DashedIdent};
21use cssparser::{CowRcStr, Parser, Token};
22use smallvec::SmallVec;
23use std::cmp;
24use std::fmt::{self, Write};
25use style_traits::values::specified::AllowedNumericType;
26use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
27
28/// The name of the mathematical function that we're parsing.
29#[derive(Clone, Copy, Debug, Parse)]
30pub enum MathFunction {
31    /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
32    Calc,
33    /// `min()`: https://drafts.csswg.org/css-values-4/#funcdef-min
34    Min,
35    /// `max()`: https://drafts.csswg.org/css-values-4/#funcdef-max
36    Max,
37    /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
38    Clamp,
39    /// `round()`: https://drafts.csswg.org/css-values-4/#funcdef-round
40    Round,
41    /// `mod()`: https://drafts.csswg.org/css-values-4/#funcdef-mod
42    Mod,
43    /// `rem()`: https://drafts.csswg.org/css-values-4/#funcdef-rem
44    Rem,
45    /// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin
46    Sin,
47    /// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos
48    Cos,
49    /// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan
50    Tan,
51    /// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin
52    Asin,
53    /// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos
54    Acos,
55    /// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan
56    Atan,
57    /// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2
58    Atan2,
59    /// `pow()`: https://drafts.csswg.org/css-values-4/#funcdef-pow
60    Pow,
61    /// `sqrt()`: https://drafts.csswg.org/css-values-4/#funcdef-sqrt
62    Sqrt,
63    /// `hypot()`: https://drafts.csswg.org/css-values-4/#funcdef-hypot
64    Hypot,
65    /// `log()`: https://drafts.csswg.org/css-values-4/#funcdef-log
66    Log,
67    /// `exp()`: https://drafts.csswg.org/css-values-4/#funcdef-exp
68    Exp,
69    /// `abs()`: https://drafts.csswg.org/css-values-4/#funcdef-abs
70    Abs,
71    /// `sign()`: https://drafts.csswg.org/css-values-4/#funcdef-sign
72    Sign,
73}
74
75/// A leaf node inside a `Calc` expression's AST.
76#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
77#[repr(u8)]
78pub enum Leaf {
79    /// `<length>`
80    Length(NoCalcLength),
81    /// `<angle>`
82    Angle(Angle),
83    /// `<time>`
84    Time(Time),
85    /// `<resolution>`
86    Resolution(Resolution),
87    /// A component of a color.
88    ColorComponent(ChannelKeyword),
89    /// `<percentage>`
90    Percentage(CSSFloat),
91    /// `<number>`
92    Number(CSSFloat),
93}
94
95impl Leaf {
96    fn as_length(&self) -> Option<&NoCalcLength> {
97        match *self {
98            Self::Length(ref l) => Some(l),
99            _ => None,
100        }
101    }
102}
103
104impl ToCss for Leaf {
105    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
106    where
107        W: Write,
108    {
109        match *self {
110            Self::Length(ref l) => l.to_css(dest),
111            Self::Number(n) => serialize_number(n, /* was_calc = */ false, dest),
112            Self::Resolution(ref r) => r.to_css(dest),
113            Self::Percentage(p) => serialize_percentage(p, dest),
114            Self::Angle(ref a) => a.to_css(dest),
115            Self::Time(ref t) => t.to_css(dest),
116            Self::ColorComponent(ref s) => s.to_css(dest),
117        }
118    }
119}
120
121/// A struct to hold a simplified `<length>` or `<percentage>` expression.
122///
123/// In some cases, e.g. DOMMatrix, we support calc(), but reject all the
124/// relative lengths, and to_computed_pixel_length_without_context() handles
125/// this case. Therefore, if you want to add a new field, please make sure this
126/// function work properly.
127#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
128#[allow(missing_docs)]
129pub struct CalcLengthPercentage {
130    #[css(skip)]
131    pub clamping_mode: AllowedNumericType,
132    pub node: CalcNode,
133}
134
135impl CalcLengthPercentage {
136    fn same_unit_length_as(a: &Self, b: &Self) -> Option<(CSSFloat, CSSFloat)> {
137        debug_assert_eq!(a.clamping_mode, b.clamping_mode);
138        debug_assert_eq!(a.clamping_mode, AllowedNumericType::All);
139
140        let a = a.node.as_leaf()?;
141        let b = b.node.as_leaf()?;
142
143        if a.sort_key() != b.sort_key() {
144            return None;
145        }
146
147        let a = a.as_length()?.unitless_value();
148        let b = b.as_length()?.unitless_value();
149        return Some((a, b));
150    }
151}
152
153impl SpecifiedValueInfo for CalcLengthPercentage {}
154
155/// Should parsing anchor-positioning functions in `calc()` be allowed?
156#[derive(Clone, Copy, PartialEq)]
157pub enum AllowAnchorPositioningFunctions {
158    /// Don't allow any anchor positioning function.
159    No,
160    /// Allow `anchor-size()` to be parsed.
161    AllowAnchorSize,
162    /// Allow `anchor()` and `anchor-size()` to be parsed.
163    AllowAnchorAndAnchorSize,
164}
165
166bitflags! {
167    /// Additional functions within math functions that are permitted to be parsed depending on
168    /// the context of parsing (e.g. Parsing `inset` allows use of `anchor()` within `calc()`).
169    #[derive(Clone, Copy, PartialEq, Eq)]
170    struct AdditionalFunctions: u8 {
171        /// `anchor()` function.
172        const ANCHOR = 1 << 0;
173        /// `anchor-size()` function.
174        const ANCHOR_SIZE = 1 << 1;
175    }
176}
177
178/// What is allowed to be parsed for math functions within in this context?
179#[derive(Clone, Copy)]
180pub struct AllowParse {
181    /// Units allowed to be parsed.
182    units: CalcUnits,
183    /// Additional functions allowed to be parsed in this context.
184    additional_functions: AdditionalFunctions,
185}
186
187impl AllowParse {
188    /// Allow only specified units to be parsed, without any additional functions.
189    pub fn new(units: CalcUnits) -> Self {
190        Self {
191            units,
192            additional_functions: AdditionalFunctions::empty(),
193        }
194    }
195
196    /// Add new units to the allowed units to be parsed.
197    fn new_including(mut self, units: CalcUnits) -> Self {
198        self.units |= units;
199        self
200    }
201
202    /// Should given unit be allowed to parse?
203    fn includes(&self, unit: CalcUnits) -> bool {
204        self.units.intersects(unit)
205    }
206}
207
208impl generic::CalcNodeLeaf for Leaf {
209    fn unit(&self) -> CalcUnits {
210        match self {
211            Leaf::Length(_) => CalcUnits::LENGTH,
212            Leaf::Angle(_) => CalcUnits::ANGLE,
213            Leaf::Time(_) => CalcUnits::TIME,
214            Leaf::Resolution(_) => CalcUnits::RESOLUTION,
215            Leaf::ColorComponent(_) => CalcUnits::COLOR_COMPONENT,
216            Leaf::Percentage(_) => CalcUnits::PERCENTAGE,
217            Leaf::Number(_) => CalcUnits::empty(),
218        }
219    }
220
221    fn unitless_value(&self) -> Option<f32> {
222        Some(match *self {
223            Self::Length(ref l) => l.unitless_value(),
224            Self::Percentage(n) | Self::Number(n) => n,
225            Self::Resolution(ref r) => r.dppx(),
226            Self::Angle(ref a) => a.degrees(),
227            Self::Time(ref t) => t.seconds(),
228            Self::ColorComponent(_) => return None,
229        })
230    }
231
232    fn new_number(value: f32) -> Self {
233        Self::Number(value)
234    }
235
236    fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option<cmp::Ordering> {
237        use self::Leaf::*;
238
239        if std::mem::discriminant(self) != std::mem::discriminant(other) {
240            return None;
241        }
242
243        if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) {
244            return None;
245        }
246
247        let self_negative = self.is_negative().unwrap_or(false);
248        if self_negative != other.is_negative().unwrap_or(false) {
249            return Some(if self_negative {
250                cmp::Ordering::Less
251            } else {
252                cmp::Ordering::Greater
253            });
254        }
255
256        match (self, other) {
257            (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
258            (&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
259            (&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()),
260            (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()),
261            (&Resolution(ref one), &Resolution(ref other)) => one.dppx().partial_cmp(&other.dppx()),
262            (&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
263            (&ColorComponent(ref one), &ColorComponent(ref other)) => one.partial_cmp(other),
264            _ => {
265                match *self {
266                    Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) |
267                    Resolution(..) | ColorComponent(..) => {},
268                }
269                unsafe {
270                    debug_unreachable!("Forgot a branch?");
271                }
272            },
273        }
274    }
275
276    fn as_number(&self) -> Option<f32> {
277        match *self {
278            Leaf::Length(_) |
279            Leaf::Angle(_) |
280            Leaf::Time(_) |
281            Leaf::Resolution(_) |
282            Leaf::Percentage(_) |
283            Leaf::ColorComponent(_) => None,
284            Leaf::Number(value) => Some(value),
285        }
286    }
287
288    fn sort_key(&self) -> SortKey {
289        match *self {
290            Self::Number(..) => SortKey::Number,
291            Self::Percentage(..) => SortKey::Percentage,
292            Self::Time(..) => SortKey::Sec,
293            Self::Resolution(..) => SortKey::Dppx,
294            Self::Angle(..) => SortKey::Deg,
295            Self::Length(ref l) => match *l {
296                NoCalcLength::Absolute(..) => SortKey::Px,
297                NoCalcLength::FontRelative(ref relative) => match *relative {
298                    FontRelativeLength::Ch(..) => SortKey::Ch,
299                    FontRelativeLength::Em(..) => SortKey::Em,
300                    FontRelativeLength::Ex(..) => SortKey::Ex,
301                    FontRelativeLength::Cap(..) => SortKey::Cap,
302                    FontRelativeLength::Ic(..) => SortKey::Ic,
303                    FontRelativeLength::Rem(..) => SortKey::Rem,
304                    FontRelativeLength::Lh(..) => SortKey::Lh,
305                    FontRelativeLength::Rlh(..) => SortKey::Rlh,
306                },
307                NoCalcLength::ViewportPercentage(ref vp) => match *vp {
308                    ViewportPercentageLength::Vh(..) => SortKey::Vh,
309                    ViewportPercentageLength::Svh(..) => SortKey::Svh,
310                    ViewportPercentageLength::Lvh(..) => SortKey::Lvh,
311                    ViewportPercentageLength::Dvh(..) => SortKey::Dvh,
312                    ViewportPercentageLength::Vw(..) => SortKey::Vw,
313                    ViewportPercentageLength::Svw(..) => SortKey::Svw,
314                    ViewportPercentageLength::Lvw(..) => SortKey::Lvw,
315                    ViewportPercentageLength::Dvw(..) => SortKey::Dvw,
316                    ViewportPercentageLength::Vmax(..) => SortKey::Vmax,
317                    ViewportPercentageLength::Svmax(..) => SortKey::Svmax,
318                    ViewportPercentageLength::Lvmax(..) => SortKey::Lvmax,
319                    ViewportPercentageLength::Dvmax(..) => SortKey::Dvmax,
320                    ViewportPercentageLength::Vmin(..) => SortKey::Vmin,
321                    ViewportPercentageLength::Svmin(..) => SortKey::Svmin,
322                    ViewportPercentageLength::Lvmin(..) => SortKey::Lvmin,
323                    ViewportPercentageLength::Dvmin(..) => SortKey::Dvmin,
324                    ViewportPercentageLength::Vb(..) => SortKey::Vb,
325                    ViewportPercentageLength::Svb(..) => SortKey::Svb,
326                    ViewportPercentageLength::Lvb(..) => SortKey::Lvb,
327                    ViewportPercentageLength::Dvb(..) => SortKey::Dvb,
328                    ViewportPercentageLength::Vi(..) => SortKey::Vi,
329                    ViewportPercentageLength::Svi(..) => SortKey::Svi,
330                    ViewportPercentageLength::Lvi(..) => SortKey::Lvi,
331                    ViewportPercentageLength::Dvi(..) => SortKey::Dvi,
332                },
333                NoCalcLength::ContainerRelative(ref cq) => match *cq {
334                    ContainerRelativeLength::Cqw(..) => SortKey::Cqw,
335                    ContainerRelativeLength::Cqh(..) => SortKey::Cqh,
336                    ContainerRelativeLength::Cqi(..) => SortKey::Cqi,
337                    ContainerRelativeLength::Cqb(..) => SortKey::Cqb,
338                    ContainerRelativeLength::Cqmin(..) => SortKey::Cqmin,
339                    ContainerRelativeLength::Cqmax(..) => SortKey::Cqmax,
340                },
341                NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
342            },
343            Self::ColorComponent(..) => SortKey::ColorComponent,
344        }
345    }
346
347    fn simplify(&mut self) {
348        if let Self::Length(NoCalcLength::Absolute(ref mut abs)) = *self {
349            *abs = AbsoluteLength::Px(abs.to_px());
350        }
351    }
352
353    /// Tries to merge one sum to another, that is, perform `x` + `y`.
354    ///
355    /// Only handles leaf nodes, it's the caller's responsibility to simplify
356    /// them before calling this if needed.
357    fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
358        use self::Leaf::*;
359
360        if std::mem::discriminant(self) != std::mem::discriminant(other) {
361            return Err(());
362        }
363
364        match (self, other) {
365            (&mut Number(ref mut one), &Number(ref other)) |
366            (&mut Percentage(ref mut one), &Percentage(ref other)) => {
367                *one += *other;
368            },
369            (&mut Angle(ref mut one), &Angle(ref other)) => {
370                *one = specified::Angle::from_calc(one.degrees() + other.degrees());
371            },
372            (&mut Time(ref mut one), &Time(ref other)) => {
373                *one = specified::Time::from_seconds(one.seconds() + other.seconds());
374            },
375            (&mut Resolution(ref mut one), &Resolution(ref other)) => {
376                *one = specified::Resolution::from_dppx(one.dppx() + other.dppx());
377            },
378            (&mut Length(ref mut one), &Length(ref other)) => {
379                *one = one.try_op(other, std::ops::Add::add)?;
380            },
381            _ => {
382                match *other {
383                    Number(..) | Percentage(..) | Angle(..) | Time(..) | Resolution(..) |
384                    Length(..) | ColorComponent(..) => {},
385                }
386                unsafe {
387                    debug_unreachable!();
388                }
389            },
390        }
391
392        Ok(())
393    }
394
395    fn try_product_in_place(&mut self, other: &mut Self) -> bool {
396        if let Self::Number(ref mut left) = *self {
397            if let Self::Number(ref right) = *other {
398                // Both sides are numbers, so we can just modify the left side.
399                *left *= *right;
400                true
401            } else {
402                // The right side is not a number, so the result should be in the units of the right
403                // side.
404                if other.map(|v| v * *left).is_ok() {
405                    std::mem::swap(self, other);
406                    true
407                } else {
408                    false
409                }
410            }
411        } else if let Self::Number(ref right) = *other {
412            // The left side is not a number, but the right side is, so the result is the left
413            // side unit.
414            self.map(|v| v * *right).is_ok()
415        } else {
416            // Neither side is a number, so a product is not possible.
417            false
418        }
419    }
420
421    fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
422    where
423        O: Fn(f32, f32) -> f32,
424    {
425        use self::Leaf::*;
426
427        if std::mem::discriminant(self) != std::mem::discriminant(other) {
428            return Err(());
429        }
430
431        match (self, other) {
432            (&Number(one), &Number(other)) => {
433                return Ok(Leaf::Number(op(one, other)));
434            },
435            (&Percentage(one), &Percentage(other)) => {
436                return Ok(Leaf::Percentage(op(one, other)));
437            },
438            (&Angle(ref one), &Angle(ref other)) => {
439                return Ok(Leaf::Angle(specified::Angle::from_calc(op(
440                    one.degrees(),
441                    other.degrees(),
442                ))));
443            },
444            (&Resolution(ref one), &Resolution(ref other)) => {
445                return Ok(Leaf::Resolution(specified::Resolution::from_dppx(op(
446                    one.dppx(),
447                    other.dppx(),
448                ))));
449            },
450            (&Time(ref one), &Time(ref other)) => {
451                return Ok(Leaf::Time(specified::Time::from_seconds(op(
452                    one.seconds(),
453                    other.seconds(),
454                ))));
455            },
456            (&Length(ref one), &Length(ref other)) => {
457                return Ok(Leaf::Length(one.try_op(other, op)?));
458            },
459            _ => {
460                match *other {
461                    Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) |
462                    Resolution(..) | ColorComponent(..) => {},
463                }
464                unsafe {
465                    debug_unreachable!();
466                }
467            },
468        }
469    }
470
471    fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
472        Ok(match self {
473            Leaf::Length(one) => *one = one.map(op),
474            Leaf::Angle(one) => *one = specified::Angle::from_calc(op(one.degrees())),
475            Leaf::Time(one) => *one = specified::Time::from_seconds(op(one.seconds())),
476            Leaf::Resolution(one) => *one = specified::Resolution::from_dppx(op(one.dppx())),
477            Leaf::Percentage(one) => *one = op(*one),
478            Leaf::Number(one) => *one = op(*one),
479            Leaf::ColorComponent(..) => return Err(()),
480        })
481    }
482}
483
484impl GenericAnchorSide<Box<CalcNode>> {
485    fn parse_in_calc<'i, 't>(
486        context: &ParserContext,
487        input: &mut Parser<'i, 't>,
488    ) -> Result<Self, ParseError<'i>> {
489        if let Ok(k) = input.try_parse(|i| AnchorSideKeyword::parse(i)) {
490            return Ok(Self::Keyword(k));
491        }
492        Ok(Self::Percentage(Box::new(CalcNode::parse_argument(
493            context,
494            input,
495            AllowParse::new(CalcUnits::PERCENTAGE),
496        )?)))
497    }
498}
499
500impl GenericAnchorFunction<Box<CalcNode>, Box<CalcNode>> {
501    fn parse_in_calc<'i, 't>(
502        context: &ParserContext,
503        input: &mut Parser<'i, 't>,
504    ) -> Result<Self, ParseError<'i>> {
505        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
506            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
507        }
508        input.parse_nested_block(|i| {
509            let target_element = i.try_parse(|i| DashedIdent::parse(context, i)).ok();
510            let side = GenericAnchorSide::parse_in_calc(context, i)?;
511            let target_element = if target_element.is_none() {
512                i.try_parse(|i| DashedIdent::parse(context, i)).ok()
513            } else {
514                target_element
515            };
516            let fallback = i
517                .try_parse(|i| {
518                    i.expect_comma()?;
519                    let node = CalcNode::parse_argument(
520                        context,
521                        i,
522                        AllowParse::new(CalcUnits::LENGTH_PERCENTAGE),
523                    )?;
524                    Ok::<Box<CalcNode>, ParseError<'i>>(Box::new(node))
525                })
526                .ok();
527            Ok(Self {
528                target_element: target_element.unwrap_or_else(DashedIdent::empty),
529                side,
530                fallback: fallback.into(),
531            })
532        })
533    }
534}
535
536impl GenericAnchorSizeFunction<Box<CalcNode>> {
537    fn parse_in_calc<'i, 't>(
538        context: &ParserContext,
539        input: &mut Parser<'i, 't>,
540    ) -> Result<Self, ParseError<'i>> {
541        if !static_prefs::pref!("layout.css.anchor-positioning.enabled") {
542            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
543        }
544        GenericAnchorSizeFunction::parse_inner(context, input, |i| {
545            CalcNode::parse_argument(context, i, AllowParse::new(CalcUnits::LENGTH_PERCENTAGE))
546                .map(|r| Box::new(r))
547        })
548    }
549}
550
551/// Specified `anchor()` function in math functions.
552pub type CalcAnchorFunction = generic::GenericCalcAnchorFunction<Leaf>;
553/// Specified `anchor-size()` function in math functions.
554pub type CalcAnchorSizeFunction = generic::GenericCalcAnchorSizeFunction<Leaf>;
555
556/// A calc node representation for specified values.
557pub type CalcNode = generic::GenericCalcNode<Leaf>;
558impl CalcNode {
559    /// Tries to parse a single element in the expression, that is, a
560    /// `<length>`, `<angle>`, `<time>`, `<percentage>`, `<resolution>`, etc.
561    ///
562    /// May return a "complex" `CalcNode`, in the presence of a parenthesized
563    /// expression, for example.
564    fn parse_one<'i, 't>(
565        context: &ParserContext,
566        input: &mut Parser<'i, 't>,
567        allowed: AllowParse,
568    ) -> Result<Self, ParseError<'i>> {
569        let location = input.current_source_location();
570        match input.next()? {
571            &Token::Number { value, .. } => Ok(CalcNode::Leaf(Leaf::Number(value))),
572            &Token::Dimension {
573                value, ref unit, ..
574            } => {
575                if allowed.includes(CalcUnits::LENGTH) {
576                    if let Ok(l) = NoCalcLength::parse_dimension(context, value, unit) {
577                        return Ok(CalcNode::Leaf(Leaf::Length(l)));
578                    }
579                }
580                if allowed.includes(CalcUnits::ANGLE) {
581                    if let Ok(a) = Angle::parse_dimension(value, unit, /* from_calc = */ true) {
582                        return Ok(CalcNode::Leaf(Leaf::Angle(a)));
583                    }
584                }
585                if allowed.includes(CalcUnits::TIME) {
586                    if let Ok(t) = Time::parse_dimension(value, unit) {
587                        return Ok(CalcNode::Leaf(Leaf::Time(t)));
588                    }
589                }
590                if allowed.includes(CalcUnits::RESOLUTION) {
591                    if let Ok(t) = Resolution::parse_dimension(value, unit) {
592                        return Ok(CalcNode::Leaf(Leaf::Resolution(t)));
593                    }
594                }
595                return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
596            },
597            &Token::Percentage { unit_value, .. } if allowed.includes(CalcUnits::PERCENTAGE) => {
598                Ok(CalcNode::Leaf(Leaf::Percentage(unit_value)))
599            },
600            &Token::ParenthesisBlock => {
601                input.parse_nested_block(|input| CalcNode::parse_argument(context, input, allowed))
602            },
603            &Token::Function(ref name)
604                if allowed
605                    .additional_functions
606                    .intersects(AdditionalFunctions::ANCHOR) &&
607                    name.eq_ignore_ascii_case("anchor") =>
608            {
609                let anchor_function = GenericAnchorFunction::parse_in_calc(context, input)?;
610                Ok(CalcNode::Anchor(Box::new(anchor_function)))
611            },
612            &Token::Function(ref name)
613                if allowed
614                    .additional_functions
615                    .intersects(AdditionalFunctions::ANCHOR_SIZE) &&
616                    name.eq_ignore_ascii_case("anchor-size") =>
617            {
618                let anchor_size_function =
619                    GenericAnchorSizeFunction::parse_in_calc(context, input)?;
620                Ok(CalcNode::AnchorSize(Box::new(anchor_size_function)))
621            },
622            &Token::Function(ref name) => {
623                let function = CalcNode::math_function(context, name, location)?;
624                CalcNode::parse(context, input, function, allowed)
625            },
626            &Token::Ident(ref ident) => {
627                let leaf = match_ignore_ascii_case! { &**ident,
628                    "e" => Leaf::Number(std::f32::consts::E),
629                    "pi" => Leaf::Number(std::f32::consts::PI),
630                    "infinity" => Leaf::Number(f32::INFINITY),
631                    "-infinity" => Leaf::Number(f32::NEG_INFINITY),
632                    "nan" => Leaf::Number(f32::NAN),
633                    _ => {
634                        if crate::color::parsing::rcs_enabled() &&
635                            allowed.includes(CalcUnits::COLOR_COMPONENT)
636                        {
637                            if let Ok(channel_keyword) = ChannelKeyword::from_ident(&ident) {
638                                Leaf::ColorComponent(channel_keyword)
639                            } else {
640                                return Err(location
641                                    .new_unexpected_token_error(Token::Ident(ident.clone())));
642                            }
643                        } else {
644                            return Err(
645                                location.new_unexpected_token_error(Token::Ident(ident.clone()))
646                            );
647                        }
648                    },
649                };
650                Ok(CalcNode::Leaf(leaf))
651            },
652            t => Err(location.new_unexpected_token_error(t.clone())),
653        }
654    }
655
656    /// Parse a top-level `calc` expression, with all nested sub-expressions.
657    ///
658    /// This is in charge of parsing, for example, `2 + 3 * 100%`.
659    pub fn parse<'i, 't>(
660        context: &ParserContext,
661        input: &mut Parser<'i, 't>,
662        function: MathFunction,
663        allowed: AllowParse,
664    ) -> Result<Self, ParseError<'i>> {
665        input.parse_nested_block(|input| {
666            match function {
667                MathFunction::Calc => Self::parse_argument(context, input, allowed),
668                MathFunction::Clamp => {
669                    let min = Self::parse_argument(context, input, allowed)?;
670                    input.expect_comma()?;
671                    let center = Self::parse_argument(context, input, allowed)?;
672                    input.expect_comma()?;
673                    let max = Self::parse_argument(context, input, allowed)?;
674                    Ok(Self::Clamp {
675                        min: Box::new(min),
676                        center: Box::new(center),
677                        max: Box::new(max),
678                    })
679                },
680                MathFunction::Round => {
681                    let strategy = input.try_parse(parse_rounding_strategy);
682
683                    // <rounding-strategy> = nearest | up | down | to-zero
684                    // https://drafts.csswg.org/css-values-4/#calc-syntax
685                    fn parse_rounding_strategy<'i, 't>(
686                        input: &mut Parser<'i, 't>,
687                    ) -> Result<RoundingStrategy, ParseError<'i>> {
688                        Ok(try_match_ident_ignore_ascii_case! { input,
689                            "nearest" => RoundingStrategy::Nearest,
690                            "up" => RoundingStrategy::Up,
691                            "down" => RoundingStrategy::Down,
692                            "to-zero" => RoundingStrategy::ToZero,
693                        })
694                    }
695
696                    if strategy.is_ok() {
697                        input.expect_comma()?;
698                    }
699
700                    let value = Self::parse_argument(context, input, allowed)?;
701
702                    // <step> defaults to the number 1 if not provided
703                    // https://drafts.csswg.org/css-values-4/#funcdef-round
704                    let step = input.try_parse(|input| {
705                        input.expect_comma()?;
706                        Self::parse_argument(context, input, allowed)
707                    });
708
709                    let step = step.unwrap_or(Self::Leaf(Leaf::Number(1.0)));
710
711                    Ok(Self::Round {
712                        strategy: strategy.unwrap_or(RoundingStrategy::Nearest),
713                        value: Box::new(value),
714                        step: Box::new(step),
715                    })
716                },
717                MathFunction::Mod | MathFunction::Rem => {
718                    let dividend = Self::parse_argument(context, input, allowed)?;
719                    input.expect_comma()?;
720                    let divisor = Self::parse_argument(context, input, allowed)?;
721
722                    let op = match function {
723                        MathFunction::Mod => ModRemOp::Mod,
724                        MathFunction::Rem => ModRemOp::Rem,
725                        _ => unreachable!(),
726                    };
727                    Ok(Self::ModRem {
728                        dividend: Box::new(dividend),
729                        divisor: Box::new(divisor),
730                        op,
731                    })
732                },
733                MathFunction::Min | MathFunction::Max => {
734                    // TODO(emilio): The common case for parse_comma_separated
735                    // is just one element, but for min / max is two, really...
736                    //
737                    // Consider adding an API to cssparser to specify the
738                    // initial vector capacity?
739                    let arguments = input.parse_comma_separated(|input| {
740                        let result = Self::parse_argument(context, input, allowed)?;
741                        Ok(result)
742                    })?;
743
744                    let op = match function {
745                        MathFunction::Min => MinMaxOp::Min,
746                        MathFunction::Max => MinMaxOp::Max,
747                        _ => unreachable!(),
748                    };
749
750                    Ok(Self::MinMax(arguments.into(), op))
751                },
752                MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => {
753                    let a = Self::parse_angle_argument(context, input)?;
754
755                    let number = match function {
756                        MathFunction::Sin => a.sin(),
757                        MathFunction::Cos => a.cos(),
758                        MathFunction::Tan => a.tan(),
759                        _ => unsafe {
760                            debug_unreachable!("We just checked!");
761                        },
762                    };
763
764                    Ok(Self::Leaf(Leaf::Number(number)))
765                },
766                MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => {
767                    let a = Self::parse_number_argument(context, input)?;
768
769                    let radians = match function {
770                        MathFunction::Asin => a.asin(),
771                        MathFunction::Acos => a.acos(),
772                        MathFunction::Atan => a.atan(),
773                        _ => unsafe {
774                            debug_unreachable!("We just checked!");
775                        },
776                    };
777
778                    Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians))))
779                },
780                MathFunction::Atan2 => {
781                    let allow_all = allowed.new_including(CalcUnits::ALL);
782                    let a = Self::parse_argument(context, input, allow_all)?;
783                    input.expect_comma()?;
784                    let b = Self::parse_argument(context, input, allow_all)?;
785
786                    let radians = Self::try_resolve(input, || {
787                        if let Ok(a) = a.to_number() {
788                            let b = b.to_number()?;
789                            return Ok(a.atan2(b));
790                        }
791
792                        if let Ok(a) = a.to_percentage() {
793                            let b = b.to_percentage()?;
794                            return Ok(a.atan2(b));
795                        }
796
797                        if let Ok(a) = a.to_time(None) {
798                            let b = b.to_time(None)?;
799                            return Ok(a.seconds().atan2(b.seconds()));
800                        }
801
802                        if let Ok(a) = a.to_angle() {
803                            let b = b.to_angle()?;
804                            return Ok(a.radians().atan2(b.radians()));
805                        }
806
807                        if let Ok(a) = a.to_resolution() {
808                            let b = b.to_resolution()?;
809                            return Ok(a.dppx().atan2(b.dppx()));
810                        }
811
812                        let a = a.into_length_or_percentage(AllowedNumericType::All)?;
813                        let b = b.into_length_or_percentage(AllowedNumericType::All)?;
814                        let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?;
815
816                        Ok(a.atan2(b))
817                    })?;
818
819                    Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians))))
820                },
821                MathFunction::Pow => {
822                    let a = Self::parse_number_argument(context, input)?;
823                    input.expect_comma()?;
824                    let b = Self::parse_number_argument(context, input)?;
825
826                    let number = a.powf(b);
827
828                    Ok(Self::Leaf(Leaf::Number(number)))
829                },
830                MathFunction::Sqrt => {
831                    let a = Self::parse_number_argument(context, input)?;
832
833                    let number = a.sqrt();
834
835                    Ok(Self::Leaf(Leaf::Number(number)))
836                },
837                MathFunction::Hypot => {
838                    let arguments = input.parse_comma_separated(|input| {
839                        let result = Self::parse_argument(context, input, allowed)?;
840                        Ok(result)
841                    })?;
842
843                    Ok(Self::Hypot(arguments.into()))
844                },
845                MathFunction::Log => {
846                    let a = Self::parse_number_argument(context, input)?;
847                    let b = input
848                        .try_parse(|input| {
849                            input.expect_comma()?;
850                            Self::parse_number_argument(context, input)
851                        })
852                        .ok();
853
854                    let number = match b {
855                        Some(b) => a.log(b),
856                        None => a.ln(),
857                    };
858
859                    Ok(Self::Leaf(Leaf::Number(number)))
860                },
861                MathFunction::Exp => {
862                    let a = Self::parse_number_argument(context, input)?;
863                    let number = a.exp();
864                    Ok(Self::Leaf(Leaf::Number(number)))
865                },
866                MathFunction::Abs => {
867                    let node = Self::parse_argument(context, input, allowed)?;
868                    Ok(Self::Abs(Box::new(node)))
869                },
870                MathFunction::Sign => {
871                    // The sign of a percentage is dependent on the percentage basis, so if
872                    // percentages aren't allowed (so there's no basis) we shouldn't allow them in
873                    // sign(). The rest of the units are safe tho.
874                    let node = Self::parse_argument(
875                        context,
876                        input,
877                        allowed.new_including(CalcUnits::ALL - CalcUnits::PERCENTAGE),
878                    )?;
879                    Ok(Self::Sign(Box::new(node)))
880                },
881            }
882        })
883    }
884
885    fn parse_angle_argument<'i, 't>(
886        context: &ParserContext,
887        input: &mut Parser<'i, 't>,
888    ) -> Result<CSSFloat, ParseError<'i>> {
889        let argument = Self::parse_argument(context, input, AllowParse::new(CalcUnits::ANGLE))?;
890        argument
891            .to_number()
892            .or_else(|()| Ok(argument.to_angle()?.radians()))
893            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
894    }
895
896    fn parse_number_argument<'i, 't>(
897        context: &ParserContext,
898        input: &mut Parser<'i, 't>,
899    ) -> Result<CSSFloat, ParseError<'i>> {
900        Self::parse_argument(context, input, AllowParse::new(CalcUnits::empty()))?
901            .to_number()
902            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
903    }
904
905    fn parse_argument<'i, 't>(
906        context: &ParserContext,
907        input: &mut Parser<'i, 't>,
908        allowed: AllowParse,
909    ) -> Result<Self, ParseError<'i>> {
910        let mut sum = SmallVec::<[CalcNode; 1]>::new();
911        let first = Self::parse_product(context, input, allowed)?;
912        sum.push(first);
913        loop {
914            let start = input.state();
915            match input.next_including_whitespace() {
916                Ok(&Token::WhiteSpace(_)) => {
917                    if input.is_exhausted() {
918                        break; // allow trailing whitespace
919                    }
920                    match *input.next()? {
921                        Token::Delim('+') => {
922                            let rhs = Self::parse_product(context, input, allowed)?;
923                            if sum.last_mut().unwrap().try_sum_in_place(&rhs).is_err() {
924                                sum.push(rhs);
925                            }
926                        },
927                        Token::Delim('-') => {
928                            let mut rhs = Self::parse_product(context, input, allowed)?;
929                            rhs.negate();
930                            if sum.last_mut().unwrap().try_sum_in_place(&rhs).is_err() {
931                                sum.push(rhs);
932                            }
933                        },
934                        _ => {
935                            input.reset(&start);
936                            break;
937                        },
938                    }
939                },
940                _ => {
941                    input.reset(&start);
942                    break;
943                },
944            }
945        }
946
947        Ok(if sum.len() == 1 {
948            sum.drain(..).next().unwrap()
949        } else {
950            Self::Sum(sum.into_boxed_slice().into())
951        })
952    }
953
954    /// Parse a top-level `calc` expression, and all the products that may
955    /// follow, and stop as soon as a non-product expression is found.
956    ///
957    /// This should parse correctly:
958    ///
959    /// * `2`
960    /// * `2 * 2`
961    /// * `2 * 2 + 2` (but will leave the `+ 2` unparsed).
962    ///
963    fn parse_product<'i, 't>(
964        context: &ParserContext,
965        input: &mut Parser<'i, 't>,
966        allowed: AllowParse,
967    ) -> Result<Self, ParseError<'i>> {
968        let mut product = SmallVec::<[CalcNode; 1]>::new();
969        let first = Self::parse_one(context, input, allowed)?;
970        product.push(first);
971
972        loop {
973            let start = input.state();
974            match input.next() {
975                Ok(&Token::Delim('*')) => {
976                    let mut rhs = Self::parse_one(context, input, allowed)?;
977
978                    // We can unwrap here, becuase we start the function by adding a node to
979                    // the list.
980                    if !product.last_mut().unwrap().try_product_in_place(&mut rhs) {
981                        product.push(rhs);
982                    }
983                },
984                Ok(&Token::Delim('/')) => {
985                    let rhs = Self::parse_one(context, input, allowed)?;
986
987                    enum InPlaceDivisionResult {
988                        /// The right was merged into the left.
989                        Merged,
990                        /// The right is not a number or could not be resolved, so the left is
991                        /// unchanged.
992                        Unchanged,
993                        /// The right was resolved, but was not a number, so the calculation is
994                        /// invalid.
995                        Invalid,
996                    }
997
998                    fn try_division_in_place(
999                        left: &mut CalcNode,
1000                        right: &CalcNode,
1001                    ) -> InPlaceDivisionResult {
1002                        if let Ok(resolved) = right.resolve() {
1003                            if let Some(number) = resolved.as_number() {
1004                                if number != 1.0 && left.is_product_distributive() {
1005                                    if left.map(|l| l / number).is_err() {
1006                                        return InPlaceDivisionResult::Invalid;
1007                                    }
1008                                    return InPlaceDivisionResult::Merged;
1009                                }
1010                            } else {
1011                                // Color components are valid denominators, but they can't resolve
1012                                // at parse time.
1013                                return if resolved.unit().contains(CalcUnits::COLOR_COMPONENT) {
1014                                    InPlaceDivisionResult::Unchanged
1015                                } else {
1016                                    InPlaceDivisionResult::Invalid
1017                                };
1018                            }
1019                        }
1020                        InPlaceDivisionResult::Unchanged
1021                    }
1022
1023                    // The right hand side of a division *must* be a number, so if we can
1024                    // already resolve it, then merge it with the last node on the product list.
1025                    // We can unwrap here, becuase we start the function by adding a node to
1026                    // the list.
1027                    match try_division_in_place(&mut product.last_mut().unwrap(), &rhs) {
1028                        InPlaceDivisionResult::Merged => {},
1029                        InPlaceDivisionResult::Unchanged => {
1030                            product.push(Self::Invert(Box::new(rhs)))
1031                        },
1032                        InPlaceDivisionResult::Invalid => {
1033                            return Err(
1034                                input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
1035                            )
1036                        },
1037                    }
1038                },
1039                _ => {
1040                    input.reset(&start);
1041                    break;
1042                },
1043            }
1044        }
1045
1046        Ok(if product.len() == 1 {
1047            product.drain(..).next().unwrap()
1048        } else {
1049            Self::Product(product.into_boxed_slice().into())
1050        })
1051    }
1052
1053    fn try_resolve<'i, 't, F>(
1054        input: &Parser<'i, 't>,
1055        closure: F,
1056    ) -> Result<CSSFloat, ParseError<'i>>
1057    where
1058        F: FnOnce() -> Result<CSSFloat, ()>,
1059    {
1060        closure().map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1061    }
1062
1063    /// Tries to simplify this expression into a `<length>` or `<percentage>`
1064    /// value.
1065    pub fn into_length_or_percentage(
1066        mut self,
1067        clamping_mode: AllowedNumericType,
1068    ) -> Result<CalcLengthPercentage, ()> {
1069        self.simplify_and_sort();
1070
1071        // Although we allow numbers inside CalcLengthPercentage, calculations that resolve to a
1072        // number result is still not allowed.
1073        let unit = self.unit()?;
1074        if !CalcUnits::LENGTH_PERCENTAGE.intersects(unit) {
1075            Err(())
1076        } else {
1077            Ok(CalcLengthPercentage {
1078                clamping_mode,
1079                node: self,
1080            })
1081        }
1082    }
1083
1084    /// Tries to simplify this expression into a `<time>` value.
1085    fn to_time(&self, clamping_mode: Option<AllowedNumericType>) -> Result<Time, ()> {
1086        let seconds = if let Leaf::Time(time) = self.resolve()? {
1087            time.seconds()
1088        } else {
1089            return Err(());
1090        };
1091
1092        Ok(Time::from_seconds_with_calc_clamping_mode(
1093            seconds,
1094            clamping_mode,
1095        ))
1096    }
1097
1098    /// Tries to simplify the expression into a `<resolution>` value.
1099    fn to_resolution(&self) -> Result<Resolution, ()> {
1100        let dppx = if let Leaf::Resolution(resolution) = self.resolve()? {
1101            resolution.dppx()
1102        } else {
1103            return Err(());
1104        };
1105
1106        Ok(Resolution::from_dppx_calc(dppx))
1107    }
1108
1109    /// Tries to simplify this expression into an `Angle` value.
1110    fn to_angle(&self) -> Result<Angle, ()> {
1111        let degrees = if let Leaf::Angle(angle) = self.resolve()? {
1112            angle.degrees()
1113        } else {
1114            return Err(());
1115        };
1116
1117        let result = Angle::from_calc(degrees);
1118        Ok(result)
1119    }
1120
1121    /// Tries to simplify this expression into a `<number>` value.
1122    fn to_number(&self) -> Result<CSSFloat, ()> {
1123        let number = if let Leaf::Number(number) = self.resolve()? {
1124            number
1125        } else {
1126            return Err(());
1127        };
1128
1129        let result = number;
1130
1131        Ok(result)
1132    }
1133
1134    /// Tries to simplify this expression into a `<percentage>` value.
1135    fn to_percentage(&self) -> Result<CSSFloat, ()> {
1136        if let Leaf::Percentage(percentage) = self.resolve()? {
1137            Ok(percentage)
1138        } else {
1139            Err(())
1140        }
1141    }
1142
1143    /// Given a function name, and the location from where the token came from,
1144    /// return a mathematical function corresponding to that name or an error.
1145    #[inline]
1146    pub fn math_function<'i>(
1147        _: &ParserContext,
1148        name: &CowRcStr<'i>,
1149        location: cssparser::SourceLocation,
1150    ) -> Result<MathFunction, ParseError<'i>> {
1151        let function = match MathFunction::from_ident(&*name) {
1152            Ok(f) => f,
1153            Err(()) => {
1154                return Err(location.new_unexpected_token_error(Token::Function(name.clone())))
1155            },
1156        };
1157
1158        Ok(function)
1159    }
1160
1161    /// Convenience parsing function for `<length> | <percentage>`, and, optionally, `anchor()`.
1162    pub fn parse_length_or_percentage<'i, 't>(
1163        context: &ParserContext,
1164        input: &mut Parser<'i, 't>,
1165        clamping_mode: AllowedNumericType,
1166        function: MathFunction,
1167        allow_anchor: AllowAnchorPositioningFunctions
1168    ) -> Result<CalcLengthPercentage, ParseError<'i>> {
1169        let allowed = if allow_anchor == AllowAnchorPositioningFunctions::No {
1170            AllowParse::new(CalcUnits::LENGTH_PERCENTAGE)
1171        } else {
1172            AllowParse {
1173                units: CalcUnits::LENGTH_PERCENTAGE,
1174                additional_functions: match allow_anchor {
1175                    AllowAnchorPositioningFunctions::No => unreachable!(),
1176                    AllowAnchorPositioningFunctions::AllowAnchorSize => AdditionalFunctions::ANCHOR_SIZE,
1177                    AllowAnchorPositioningFunctions::AllowAnchorAndAnchorSize => AdditionalFunctions::ANCHOR | AdditionalFunctions::ANCHOR_SIZE,
1178                },
1179            }
1180        };
1181        Self::parse(context, input, function, allowed)?
1182            .into_length_or_percentage(clamping_mode)
1183            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1184    }
1185
1186    /// Convenience parsing function for percentages.
1187    pub fn parse_percentage<'i, 't>(
1188        context: &ParserContext,
1189        input: &mut Parser<'i, 't>,
1190        function: MathFunction,
1191    ) -> Result<CSSFloat, ParseError<'i>> {
1192        Self::parse(context, input, function, AllowParse::new(CalcUnits::PERCENTAGE))?
1193            .to_percentage()
1194            .map(crate::values::normalize)
1195            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1196    }
1197
1198    /// Convenience parsing function for `<length>`.
1199    pub fn parse_length<'i, 't>(
1200        context: &ParserContext,
1201        input: &mut Parser<'i, 't>,
1202        clamping_mode: AllowedNumericType,
1203        function: MathFunction,
1204    ) -> Result<CalcLengthPercentage, ParseError<'i>> {
1205        Self::parse(context, input, function, AllowParse::new(CalcUnits::LENGTH))?
1206            .into_length_or_percentage(clamping_mode)
1207            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1208    }
1209
1210    /// Convenience parsing function for `<number>`.
1211    pub fn parse_number<'i, 't>(
1212        context: &ParserContext,
1213        input: &mut Parser<'i, 't>,
1214        function: MathFunction,
1215    ) -> Result<CSSFloat, ParseError<'i>> {
1216        Self::parse(context, input, function, AllowParse::new(CalcUnits::empty()))?
1217            .to_number()
1218            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1219    }
1220
1221    /// Convenience parsing function for `<angle>`.
1222    pub fn parse_angle<'i, 't>(
1223        context: &ParserContext,
1224        input: &mut Parser<'i, 't>,
1225        function: MathFunction,
1226    ) -> Result<Angle, ParseError<'i>> {
1227        Self::parse(context, input, function, AllowParse::new(CalcUnits::ANGLE))?
1228            .to_angle()
1229            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1230    }
1231
1232    /// Convenience parsing function for `<time>`.
1233    pub fn parse_time<'i, 't>(
1234        context: &ParserContext,
1235        input: &mut Parser<'i, 't>,
1236        clamping_mode: AllowedNumericType,
1237        function: MathFunction,
1238    ) -> Result<Time, ParseError<'i>> {
1239        Self::parse(context, input, function, AllowParse::new(CalcUnits::TIME))?
1240            .to_time(Some(clamping_mode))
1241            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1242    }
1243
1244    /// Convenience parsing function for `<resolution>`.
1245    pub fn parse_resolution<'i, 't>(
1246        context: &ParserContext,
1247        input: &mut Parser<'i, 't>,
1248        function: MathFunction,
1249    ) -> Result<Resolution, ParseError<'i>> {
1250        Self::parse(context, input, function, AllowParse::new(CalcUnits::RESOLUTION))?
1251            .to_resolution()
1252            .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1253    }
1254}