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