lightningcss/values/
calc.rs

1//! Mathematical calculation functions and expressions.
2
3use crate::compat::Feature;
4use crate::error::{ParserError, PrinterError};
5use crate::macros::enum_property;
6use crate::printer::Printer;
7use crate::targets::{should_compile, Browsers};
8use crate::traits::private::AddInternal;
9use crate::traits::{IsCompatible, Parse, Sign, ToCss, TryMap, TryOp, TrySign};
10#[cfg(feature = "visitor")]
11use crate::visitor::Visit;
12use cssparser::*;
13
14use super::angle::Angle;
15use super::length::Length;
16use super::number::CSSNumber;
17use super::percentage::Percentage;
18use super::time::Time;
19
20/// A CSS [math function](https://www.w3.org/TR/css-values-4/#math-function).
21///
22/// Math functions may be used in most properties and values that accept numeric
23/// values, including lengths, percentages, angles, times, etc.
24#[derive(Debug, Clone, PartialEq)]
25#[cfg_attr(feature = "visitor", derive(Visit))]
26#[cfg_attr(
27  feature = "serde",
28  derive(serde::Serialize, serde::Deserialize),
29  serde(tag = "type", content = "value", rename_all = "kebab-case")
30)]
31#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
32#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
33pub enum MathFunction<V> {
34  /// The [`calc()`](https://www.w3.org/TR/css-values-4/#calc-func) function.
35  Calc(Calc<V>),
36  /// The [`min()`](https://www.w3.org/TR/css-values-4/#funcdef-min) function.
37  Min(Vec<Calc<V>>),
38  /// The [`max()`](https://www.w3.org/TR/css-values-4/#funcdef-max) function.
39  Max(Vec<Calc<V>>),
40  /// The [`clamp()`](https://www.w3.org/TR/css-values-4/#funcdef-clamp) function.
41  Clamp(Calc<V>, Calc<V>, Calc<V>),
42  /// The [`round()`](https://www.w3.org/TR/css-values-4/#funcdef-round) function.
43  Round(RoundingStrategy, Calc<V>, Calc<V>),
44  /// The [`rem()`](https://www.w3.org/TR/css-values-4/#funcdef-rem) function.
45  Rem(Calc<V>, Calc<V>),
46  /// The [`mod()`](https://www.w3.org/TR/css-values-4/#funcdef-mod) function.
47  Mod(Calc<V>, Calc<V>),
48  /// The [`abs()`](https://drafts.csswg.org/css-values-4/#funcdef-abs) function.
49  Abs(Calc<V>),
50  /// The [`sign()`](https://drafts.csswg.org/css-values-4/#funcdef-sign) function.
51  Sign(Calc<V>),
52  /// The [`hypot()`](https://drafts.csswg.org/css-values-4/#funcdef-hypot) function.
53  Hypot(Vec<Calc<V>>),
54}
55
56impl<V: IsCompatible> IsCompatible for MathFunction<V> {
57  fn is_compatible(&self, browsers: Browsers) -> bool {
58    match self {
59      MathFunction::Calc(v) => Feature::CalcFunction.is_compatible(browsers) && v.is_compatible(browsers),
60      MathFunction::Min(v) => {
61        Feature::MinFunction.is_compatible(browsers) && v.iter().all(|v| v.is_compatible(browsers))
62      }
63      MathFunction::Max(v) => {
64        Feature::MaxFunction.is_compatible(browsers) && v.iter().all(|v| v.is_compatible(browsers))
65      }
66      MathFunction::Clamp(a, b, c) => {
67        Feature::ClampFunction.is_compatible(browsers)
68          && a.is_compatible(browsers)
69          && b.is_compatible(browsers)
70          && c.is_compatible(browsers)
71      }
72      MathFunction::Round(_, a, b) => {
73        Feature::RoundFunction.is_compatible(browsers) && a.is_compatible(browsers) && b.is_compatible(browsers)
74      }
75      MathFunction::Rem(a, b) => {
76        Feature::RemFunction.is_compatible(browsers) && a.is_compatible(browsers) && b.is_compatible(browsers)
77      }
78      MathFunction::Mod(a, b) => {
79        Feature::ModFunction.is_compatible(browsers) && a.is_compatible(browsers) && b.is_compatible(browsers)
80      }
81      MathFunction::Abs(v) => Feature::AbsFunction.is_compatible(browsers) && v.is_compatible(browsers),
82      MathFunction::Sign(v) => Feature::SignFunction.is_compatible(browsers) && v.is_compatible(browsers),
83      MathFunction::Hypot(v) => {
84        Feature::HypotFunction.is_compatible(browsers) && v.iter().all(|v| v.is_compatible(browsers))
85      }
86    }
87  }
88}
89
90enum_property! {
91  /// A [rounding strategy](https://www.w3.org/TR/css-values-4/#typedef-rounding-strategy),
92  /// as used in the `round()` function.
93  pub enum RoundingStrategy {
94    /// Round to the nearest integer.
95    Nearest,
96    /// Round up (ceil).
97    Up,
98    /// Round down (floor).
99    Down,
100    /// Round toward zero (truncate).
101    ToZero,
102  }
103}
104
105impl Default for RoundingStrategy {
106  fn default() -> Self {
107    RoundingStrategy::Nearest
108  }
109}
110
111fn round(value: f32, to: f32, strategy: RoundingStrategy) -> f32 {
112  let v = value / to;
113  match strategy {
114    RoundingStrategy::Down => v.floor() * to,
115    RoundingStrategy::Up => v.ceil() * to,
116    RoundingStrategy::Nearest => v.round() * to,
117    RoundingStrategy::ToZero => v.trunc() * to,
118  }
119}
120
121fn modulo(a: f32, b: f32) -> f32 {
122  ((a % b) + b) % b
123}
124
125impl<V: ToCss + std::ops::Mul<f32, Output = V> + TrySign + Clone + std::fmt::Debug> ToCss for MathFunction<V> {
126  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
127  where
128    W: std::fmt::Write,
129  {
130    match self {
131      MathFunction::Calc(calc) => {
132        dest.write_str("calc(")?;
133        calc.to_css(dest)?;
134        dest.write_char(')')
135      }
136      MathFunction::Min(args) => {
137        dest.write_str("min(")?;
138        let mut first = true;
139        for arg in args {
140          if first {
141            first = false;
142          } else {
143            dest.delim(',', false)?;
144          }
145          arg.to_css(dest)?;
146        }
147        dest.write_char(')')
148      }
149      MathFunction::Max(args) => {
150        dest.write_str("max(")?;
151        let mut first = true;
152        for arg in args {
153          if first {
154            first = false;
155          } else {
156            dest.delim(',', false)?;
157          }
158          arg.to_css(dest)?;
159        }
160        dest.write_char(')')
161      }
162      MathFunction::Clamp(a, b, c) => {
163        // If clamp() is unsupported by targets, output min()/max()
164        if should_compile!(dest.targets, ClampFunction) {
165          dest.write_str("max(")?;
166          a.to_css(dest)?;
167          dest.delim(',', false)?;
168          dest.write_str("min(")?;
169          b.to_css(dest)?;
170          dest.delim(',', false)?;
171          c.to_css(dest)?;
172          dest.write_str("))")?;
173          return Ok(());
174        }
175
176        dest.write_str("clamp(")?;
177        a.to_css(dest)?;
178        dest.delim(',', false)?;
179        b.to_css(dest)?;
180        dest.delim(',', false)?;
181        c.to_css(dest)?;
182        dest.write_char(')')
183      }
184      MathFunction::Round(strategy, a, b) => {
185        dest.write_str("round(")?;
186        if *strategy != RoundingStrategy::default() {
187          strategy.to_css(dest)?;
188          dest.delim(',', false)?;
189        }
190        a.to_css(dest)?;
191        dest.delim(',', false)?;
192        b.to_css(dest)?;
193        dest.write_char(')')
194      }
195      MathFunction::Rem(a, b) => {
196        dest.write_str("rem(")?;
197        a.to_css(dest)?;
198        dest.delim(',', false)?;
199        b.to_css(dest)?;
200        dest.write_char(')')
201      }
202      MathFunction::Mod(a, b) => {
203        dest.write_str("mod(")?;
204        a.to_css(dest)?;
205        dest.delim(',', false)?;
206        b.to_css(dest)?;
207        dest.write_char(')')
208      }
209      MathFunction::Abs(v) => {
210        dest.write_str("abs(")?;
211        v.to_css(dest)?;
212        dest.write_char(')')
213      }
214      MathFunction::Sign(v) => {
215        dest.write_str("sign(")?;
216        v.to_css(dest)?;
217        dest.write_char(')')
218      }
219      MathFunction::Hypot(args) => {
220        dest.write_str("hypot(")?;
221        let mut first = true;
222        for arg in args {
223          if first {
224            first = false;
225          } else {
226            dest.delim(',', false)?;
227          }
228          arg.to_css(dest)?;
229        }
230        dest.write_char(')')
231      }
232    }
233  }
234}
235
236/// A mathematical expression used within the [`calc()`](https://www.w3.org/TR/css-values-4/#calc-func) function.
237///
238/// This type supports generic value types. Values such as [Length](super::length::Length), [Percentage](super::percentage::Percentage),
239/// [Time](super::time::Time), and [Angle](super::angle::Angle) support `calc()` expressions.
240#[derive(Debug, Clone, PartialEq)]
241#[cfg_attr(feature = "visitor", derive(Visit))]
242#[cfg_attr(
243  feature = "serde",
244  derive(serde::Serialize, serde::Deserialize),
245  serde(tag = "type", content = "value", rename_all = "kebab-case")
246)]
247#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
248#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
249pub enum Calc<V> {
250  /// A literal value.
251  Value(Box<V>),
252  /// A literal number.
253  Number(CSSNumber),
254  /// A sum of two calc expressions.
255  #[cfg_attr(feature = "visitor", skip_type)]
256  Sum(Box<Calc<V>>, Box<Calc<V>>),
257  /// A product of a number and another calc expression.
258  #[cfg_attr(feature = "visitor", skip_type)]
259  Product(CSSNumber, Box<Calc<V>>),
260  /// A math function, such as `calc()`, `min()`, or `max()`.
261  #[cfg_attr(feature = "visitor", skip_type)]
262  Function(Box<MathFunction<V>>),
263}
264
265impl<V: IsCompatible> IsCompatible for Calc<V> {
266  fn is_compatible(&self, browsers: Browsers) -> bool {
267    match self {
268      Calc::Sum(a, b) => a.is_compatible(browsers) && b.is_compatible(browsers),
269      Calc::Product(_, v) => v.is_compatible(browsers),
270      Calc::Function(f) => f.is_compatible(browsers),
271      Calc::Value(v) => v.is_compatible(browsers),
272      Calc::Number(..) => true,
273    }
274  }
275}
276
277enum_property! {
278  /// A mathematical constant.
279  pub enum Constant {
280    /// The base of the natural logarithm
281    "e": E,
282    /// The ratio of a circle’s circumference to its diameter
283    "pi": Pi,
284    /// infinity
285    "infinity": Infinity,
286    /// -infinity
287    "-infinity": NegativeInfinity,
288    /// Not a number.
289    "nan": Nan,
290  }
291}
292
293impl Into<f32> for Constant {
294  fn into(self) -> f32 {
295    use std::f32::consts;
296    use Constant::*;
297    match self {
298      E => consts::E,
299      Pi => consts::PI,
300      Infinity => f32::INFINITY,
301      NegativeInfinity => -f32::INFINITY,
302      Nan => f32::NAN,
303    }
304  }
305}
306
307impl<
308    'i,
309    V: Parse<'i>
310      + std::ops::Mul<f32, Output = V>
311      + AddInternal
312      + TryOp
313      + TryMap
314      + TrySign
315      + std::cmp::PartialOrd<V>
316      + Into<Calc<V>>
317      + From<Calc<V>>
318      + TryFrom<Angle>
319      + Clone
320      + std::fmt::Debug,
321  > Parse<'i> for Calc<V>
322{
323  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
324    Self::parse_with(input, |_| None)
325  }
326}
327
328impl<
329    'i,
330    V: Parse<'i>
331      + std::ops::Mul<f32, Output = V>
332      + AddInternal
333      + TryOp
334      + TryMap
335      + TrySign
336      + std::cmp::PartialOrd<V>
337      + Into<Calc<V>>
338      + From<Calc<V>>
339      + TryFrom<Angle>
340      + Clone
341      + std::fmt::Debug,
342  > Calc<V>
343{
344  pub(crate) fn parse_with<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
345    input: &mut Parser<'i, 't>,
346    parse_ident: Parse,
347  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
348    let location = input.current_source_location();
349    let f = input.expect_function()?;
350    match_ignore_ascii_case! { &f,
351      "calc" => {
352        let calc = input.parse_nested_block(|input| Calc::parse_sum(input, parse_ident))?;
353        match calc {
354          Calc::Value(_) | Calc::Number(_) => Ok(calc),
355          _ => Ok(Calc::Function(Box::new(MathFunction::Calc(calc))))
356        }
357      },
358      "min" => {
359        let mut args = input.parse_nested_block(|input| input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident)))?;
360        let mut reduced = Calc::reduce_args(&mut args, std::cmp::Ordering::Less);
361        if reduced.len() == 1 {
362          return Ok(reduced.remove(0))
363        }
364        Ok(Calc::Function(Box::new(MathFunction::Min(reduced))))
365      },
366      "max" => {
367        let mut args = input.parse_nested_block(|input| input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident)))?;
368        let mut reduced = Calc::reduce_args(&mut args, std::cmp::Ordering::Greater);
369        if reduced.len() == 1 {
370          return Ok(reduced.remove(0))
371        }
372        Ok(Calc::Function(Box::new(MathFunction::Max(reduced))))
373      },
374      "clamp" => {
375        let (mut min, mut center, mut max) = input.parse_nested_block(|input| {
376          let min = Some(Calc::parse_sum(input, parse_ident)?);
377          input.expect_comma()?;
378          let center: Calc<V> = Calc::parse_sum(input, parse_ident)?;
379          input.expect_comma()?;
380          let max = Some(Calc::parse_sum(input, parse_ident)?);
381          Ok((min, center, max))
382        })?;
383
384        // According to the spec, the minimum should "win" over the maximum if they are in the wrong order.
385        let cmp = if let (Some(Calc::Value(max_val)), Calc::Value(center_val)) = (&max, &center) {
386          center_val.partial_cmp(&max_val)
387        } else {
388          None
389        };
390
391        // If center is known to be greater than the maximum, replace it with maximum and remove the max argument.
392        // Otherwise, if center is known to be less than the maximum, remove the max argument.
393        match cmp {
394          Some(std::cmp::Ordering::Greater) => {
395            center = std::mem::take(&mut max).unwrap();
396          }
397          Some(_) => {
398            max = None;
399          }
400          None => {}
401        }
402
403        let cmp = if let (Some(Calc::Value(min_val)), Calc::Value(center_val)) = (&min, &center) {
404          center_val.partial_cmp(&min_val)
405        } else {
406          None
407        };
408
409        // If center is known to be less than the minimum, replace it with minimum and remove the min argument.
410        // Otherwise, if center is known to be greater than the minimum, remove the min argument.
411        match cmp {
412          Some(std::cmp::Ordering::Less) => {
413            center = std::mem::take(&mut min).unwrap();
414          }
415          Some(_) => {
416            min = None;
417          }
418          None => {}
419        }
420
421        // Generate clamp(), min(), max(), or value depending on which arguments are left.
422        match (min, max) {
423          (None, None) => Ok(center),
424          (Some(min), None) => Ok(Calc::Function(Box::new(MathFunction::Max(vec![min, center])))),
425          (None, Some(max)) => Ok(Calc::Function(Box::new(MathFunction::Min(vec![center, max])))),
426          (Some(min), Some(max)) => Ok(Calc::Function(Box::new(MathFunction::Clamp(min, center, max))))
427        }
428      },
429      "round" => {
430        input.parse_nested_block(|input| {
431          let strategy = if let Ok(s) = input.try_parse(RoundingStrategy::parse) {
432            input.expect_comma()?;
433            s
434          } else {
435            RoundingStrategy::default()
436          };
437
438          Self::parse_math_fn(
439            input,
440            |a, b| round(a, b, strategy),
441            |a, b| MathFunction::Round(strategy, a, b),
442            parse_ident
443          )
444        })
445      },
446      "rem" => {
447        input.parse_nested_block(|input| {
448          Self::parse_math_fn(input, std::ops::Rem::rem, MathFunction::Rem, parse_ident)
449        })
450      },
451      "mod" => {
452        input.parse_nested_block(|input| {
453          Self::parse_math_fn(input, modulo, MathFunction::Mod, parse_ident)
454        })
455      },
456      "sin" => Self::parse_trig(input, f32::sin, false, parse_ident),
457      "cos" => Self::parse_trig(input, f32::cos, false, parse_ident),
458      "tan" => Self::parse_trig(input, f32::tan, false, parse_ident),
459      "asin" => Self::parse_trig(input, f32::asin, true, parse_ident),
460      "acos" => Self::parse_trig(input, f32::acos, true, parse_ident),
461      "atan" => Self::parse_trig(input, f32::atan, true, parse_ident),
462      "atan2" => {
463        input.parse_nested_block(|input| {
464          let res = Self::parse_atan2(input, parse_ident)?;
465          if let Ok(v) = V::try_from(res) {
466            return Ok(Calc::Value(Box::new(v)))
467          }
468
469          Err(input.new_custom_error(ParserError::InvalidValue))
470        })
471      },
472      "pow" => {
473        input.parse_nested_block(|input| {
474          let a = Self::parse_numeric(input, parse_ident)?;
475          input.expect_comma()?;
476          let b = Self::parse_numeric(input, parse_ident)?;
477          Ok(Calc::Number(a.powf(b)))
478        })
479      },
480      "log" => {
481        input.parse_nested_block(|input| {
482          let value = Self::parse_numeric(input, parse_ident)?;
483          if input.try_parse(|input| input.expect_comma()).is_ok() {
484            let base = Self::parse_numeric(input, parse_ident)?;
485            Ok(Calc::Number(value.log(base)))
486          } else {
487            Ok(Calc::Number(value.ln()))
488          }
489        })
490      },
491      "sqrt" => Self::parse_numeric_fn(input, f32::sqrt, parse_ident),
492      "exp" => Self::parse_numeric_fn(input, f32::exp, parse_ident),
493      "hypot" => {
494        input.parse_nested_block(|input| {
495          let args: Vec<Self> = input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident))?;
496          Self::parse_hypot(&args)?
497            .map_or_else(
498              || Ok(Calc::Function(Box::new(MathFunction::Hypot(args)))),
499              |v| Ok(v)
500            )
501        })
502      },
503      "abs" => {
504        input.parse_nested_block(|input| {
505          let v: Calc<V> = Self::parse_sum(input, parse_ident)?;
506          Self::apply_map(&v, f32::abs)
507            .map_or_else(
508              || Ok(Calc::Function(Box::new(MathFunction::Abs(v)))),
509              |v| Ok(v)
510            )
511        })
512      },
513      "sign" => {
514        input.parse_nested_block(|input| {
515          let v: Calc<V> = Self::parse_sum(input, parse_ident)?;
516          match &v {
517            Calc::Number(n) => return Ok(Calc::Number(n.sign())),
518            Calc::Value(v) => {
519              // First map so we ignore percentages, which must be resolved to their
520              // computed value in order to determine the sign.
521              if let Some(v) = v.try_map(|s| s.sign()) {
522                // sign() always resolves to a number.
523                return Ok(Calc::Number(v.try_sign().unwrap()));
524              }
525            }
526            _ => {}
527          }
528
529          Ok(Calc::Function(Box::new(MathFunction::Sign(v))))
530        })
531      },
532       _ => Err(location.new_unexpected_token_error(Token::Ident(f.clone()))),
533    }
534  }
535
536  fn parse_sum<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
537    input: &mut Parser<'i, 't>,
538    parse_ident: Parse,
539  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
540    let mut cur: Calc<V> = Calc::parse_product(input, parse_ident)?;
541    loop {
542      let start = input.state();
543      match input.next_including_whitespace() {
544        Ok(&Token::WhiteSpace(_)) => {
545          if input.is_exhausted() {
546            break; // allow trailing whitespace
547          }
548          match *input.next()? {
549            Token::Delim('+') => {
550              let next = Calc::parse_product(input, parse_ident)?;
551              cur = cur.add(next);
552            }
553            Token::Delim('-') => {
554              let mut rhs = Calc::parse_product(input, parse_ident)?;
555              rhs = rhs * -1.0;
556              cur = cur.add(rhs);
557            }
558            ref t => {
559              let t = t.clone();
560              return Err(input.new_unexpected_token_error(t));
561            }
562          }
563        }
564        _ => {
565          input.reset(&start);
566          break;
567        }
568      }
569    }
570    Ok(cur)
571  }
572
573  fn parse_product<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
574    input: &mut Parser<'i, 't>,
575    parse_ident: Parse,
576  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
577    let mut node = Calc::parse_value(input, parse_ident)?;
578    loop {
579      let start = input.state();
580      match input.next() {
581        Ok(&Token::Delim('*')) => {
582          // At least one of the operands must be a number.
583          let rhs = Self::parse_value(input, parse_ident)?;
584          if let Calc::Number(val) = rhs {
585            node = node * val;
586          } else if let Calc::Number(val) = node {
587            node = rhs;
588            node = node * val;
589          } else {
590            return Err(input.new_unexpected_token_error(Token::Delim('*')));
591          }
592        }
593        Ok(&Token::Delim('/')) => {
594          let rhs = Self::parse_value(input, parse_ident)?;
595          if let Calc::Number(val) = rhs {
596            if val != 0.0 {
597              node = node * (1.0 / val);
598              continue;
599            }
600          }
601          return Err(input.new_custom_error(ParserError::InvalidValue));
602        }
603        _ => {
604          input.reset(&start);
605          break;
606        }
607      }
608    }
609    Ok(node)
610  }
611
612  fn parse_value<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
613    input: &mut Parser<'i, 't>,
614    parse_ident: Parse,
615  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
616    // Parse nested calc() and other math functions.
617    if let Ok(calc) = input.try_parse(Self::parse) {
618      match calc {
619        Calc::Function(f) => {
620          return Ok(match *f {
621            MathFunction::Calc(c) => c,
622            _ => Calc::Function(f),
623          })
624        }
625        c => return Ok(c),
626      }
627    }
628
629    if input.try_parse(|input| input.expect_parenthesis_block()).is_ok() {
630      return input.parse_nested_block(|input| Calc::parse_sum(input, parse_ident));
631    }
632
633    if let Ok(num) = input.try_parse(|input| input.expect_number()) {
634      return Ok(Calc::Number(num));
635    }
636
637    if let Ok(constant) = input.try_parse(Constant::parse) {
638      return Ok(Calc::Number(constant.into()));
639    }
640
641    let location = input.current_source_location();
642    if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
643      if let Some(v) = parse_ident(ident.as_ref()) {
644        return Ok(v);
645      }
646
647      return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));
648    }
649
650    let value = input.try_parse(V::parse)?;
651    Ok(Calc::Value(Box::new(value)))
652  }
653
654  fn reduce_args(args: &mut Vec<Calc<V>>, cmp: std::cmp::Ordering) -> Vec<Calc<V>> {
655    // Reduces the arguments of a min() or max() expression, combining compatible values.
656    // e.g. min(1px, 1em, 2px, 3in) => min(1px, 1em)
657    let mut reduced: Vec<Calc<V>> = vec![];
658    for arg in args.drain(..) {
659      let mut found = None;
660      match &arg {
661        Calc::Value(val) => {
662          for b in reduced.iter_mut() {
663            if let Calc::Value(v) = b {
664              match val.partial_cmp(v) {
665                Some(ord) if ord == cmp => {
666                  found = Some(Some(b));
667                  break;
668                }
669                Some(_) => {
670                  found = Some(None);
671                  break;
672                }
673                None => {}
674              }
675            }
676          }
677        }
678        _ => {}
679      }
680      if let Some(r) = found {
681        if let Some(r) = r {
682          *r = arg
683        }
684      } else {
685        reduced.push(arg)
686      }
687    }
688    reduced
689  }
690
691  fn parse_math_fn<
692    't,
693    O: FnOnce(f32, f32) -> f32,
694    F: FnOnce(Calc<V>, Calc<V>) -> MathFunction<V>,
695    Parse: Copy + Fn(&str) -> Option<Calc<V>>,
696  >(
697    input: &mut Parser<'i, 't>,
698    op: O,
699    fallback: F,
700    parse_ident: Parse,
701  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
702    let a: Calc<V> = Calc::parse_sum(input, parse_ident)?;
703    input.expect_comma()?;
704    let b: Calc<V> = Calc::parse_sum(input, parse_ident)?;
705
706    Ok(Self::apply_op(&a, &b, op).unwrap_or_else(|| Calc::Function(Box::new(fallback(a, b)))))
707  }
708
709  fn apply_op<'t, O: FnOnce(f32, f32) -> f32>(a: &Calc<V>, b: &Calc<V>, op: O) -> Option<Self> {
710    match (a, b) {
711      (Calc::Value(a), Calc::Value(b)) => {
712        if let Some(v) = a.try_op(&**b, op) {
713          return Some(Calc::Value(Box::new(v)));
714        }
715      }
716      (Calc::Number(a), Calc::Number(b)) => return Some(Calc::Number(op(*a, *b))),
717      _ => {}
718    }
719
720    None
721  }
722
723  fn apply_map<'t, O: FnOnce(f32) -> f32>(v: &Calc<V>, op: O) -> Option<Self> {
724    match v {
725      Calc::Number(n) => return Some(Calc::Number(op(*n))),
726      Calc::Value(v) => {
727        if let Some(v) = v.try_map(op) {
728          return Some(Calc::Value(Box::new(v)));
729        }
730      }
731      _ => {}
732    }
733
734    None
735  }
736
737  fn parse_trig<'t, F: FnOnce(f32) -> f32, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
738    input: &mut Parser<'i, 't>,
739    f: F,
740    to_angle: bool,
741    parse_ident: Parse,
742  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
743    input.parse_nested_block(|input| {
744      let v: Calc<Angle> = Calc::parse_sum(input, |v| {
745        parse_ident(v).and_then(|v| match v {
746          Calc::Number(v) => Some(Calc::Number(v)),
747          _ => None,
748        })
749      })?;
750      let rad = match v {
751        Calc::Value(angle) if !to_angle => f(angle.to_radians()),
752        Calc::Number(v) => f(v),
753        _ => return Err(input.new_custom_error(ParserError::InvalidValue)),
754      };
755
756      if to_angle && !rad.is_nan() {
757        if let Ok(v) = V::try_from(Angle::Rad(rad)) {
758          return Ok(Calc::Value(Box::new(v)));
759        } else {
760          return Err(input.new_custom_error(ParserError::InvalidValue));
761        }
762      } else {
763        Ok(Calc::Number(rad))
764      }
765    })
766  }
767
768  fn parse_numeric<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
769    input: &mut Parser<'i, 't>,
770    parse_ident: Parse,
771  ) -> Result<f32, ParseError<'i, ParserError<'i>>> {
772    let v: Calc<CSSNumber> = Calc::parse_sum(input, |v| {
773      parse_ident(v).and_then(|v| match v {
774        Calc::Number(v) => Some(Calc::Number(v)),
775        _ => None,
776      })
777    })?;
778    match v {
779      Calc::Number(n) => Ok(n),
780      Calc::Value(v) => Ok(*v),
781      _ => Err(input.new_custom_error(ParserError::InvalidValue)),
782    }
783  }
784
785  fn parse_numeric_fn<'t, F: FnOnce(f32) -> f32, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
786    input: &mut Parser<'i, 't>,
787    f: F,
788    parse_ident: Parse,
789  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
790    input.parse_nested_block(|input| {
791      let v = Self::parse_numeric(input, parse_ident)?;
792      Ok(Calc::Number(f(v)))
793    })
794  }
795
796  fn parse_atan2<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
797    input: &mut Parser<'i, 't>,
798    parse_ident: Parse,
799  ) -> Result<Angle, ParseError<'i, ParserError<'i>>> {
800    // atan2 supports arguments of any <number>, <dimension>, or <percentage>, even ones that wouldn't
801    // normally be supported by V. The only requirement is that the arguments be of the same type.
802    // Try parsing with each type, and return the first one that parses successfully.
803    if let Ok(v) = input.try_parse(|input| Calc::<Length>::parse_atan2_args(input, |_| None)) {
804      return Ok(v);
805    }
806
807    if let Ok(v) = input.try_parse(|input| Calc::<Percentage>::parse_atan2_args(input, |_| None)) {
808      return Ok(v);
809    }
810
811    if let Ok(v) = input.try_parse(|input| Calc::<Angle>::parse_atan2_args(input, |_| None)) {
812      return Ok(v);
813    }
814
815    if let Ok(v) = input.try_parse(|input| Calc::<Time>::parse_atan2_args(input, |_| None)) {
816      return Ok(v);
817    }
818
819    Calc::<CSSNumber>::parse_atan2_args(input, |v| {
820      parse_ident(v).and_then(|v| match v {
821        Calc::Number(v) => Some(Calc::Number(v)),
822        _ => None,
823      })
824    })
825  }
826
827  fn parse_atan2_args<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
828    input: &mut Parser<'i, 't>,
829    parse_ident: Parse,
830  ) -> Result<Angle, ParseError<'i, ParserError<'i>>> {
831    let a = Calc::<V>::parse_sum(input, parse_ident)?;
832    input.expect_comma()?;
833    let b = Calc::<V>::parse_sum(input, parse_ident)?;
834
835    match (&a, &b) {
836      (Calc::Value(a), Calc::Value(b)) => {
837        if let Some(v) = a.try_op_to(&**b, |a, b| Angle::Rad(a.atan2(b))) {
838          return Ok(v);
839        }
840      }
841      (Calc::Number(a), Calc::Number(b)) => return Ok(Angle::Rad(a.atan2(*b))),
842      _ => {}
843    }
844
845    // We don't have a way to represent arguments that aren't angles, so just error.
846    // This will fall back to an unparsed property, leaving the atan2() function intact.
847    Err(input.new_custom_error(ParserError::InvalidValue))
848  }
849
850  fn parse_hypot<'t>(args: &Vec<Self>) -> Result<Option<Self>, ParseError<'i, ParserError<'i>>> {
851    if args.len() == 1 {
852      return Ok(Some(args[0].clone()));
853    }
854
855    if args.len() == 2 {
856      return Ok(Self::apply_op(&args[0], &args[1], f32::hypot));
857    }
858
859    let mut iter = args.iter();
860    let first = match Self::apply_map(&iter.next().unwrap(), |v| v.powi(2)) {
861      Some(v) => v,
862      None => return Ok(None),
863    };
864    let sum = iter.try_fold(first, |acc, arg| {
865      Self::apply_op(&acc, &arg, |a, b| a + b.powi(2)).map_or_else(|| Err(()), |v| Ok(v))
866    });
867
868    let sum = match sum {
869      Ok(s) => s,
870      Err(_) => return Ok(None),
871    };
872
873    Ok(Self::apply_map(&sum, f32::sqrt))
874  }
875}
876
877impl<V: std::ops::Mul<f32, Output = V>> std::ops::Mul<f32> for Calc<V> {
878  type Output = Self;
879
880  fn mul(self, other: f32) -> Self {
881    if other == 1.0 {
882      return self;
883    }
884
885    match self {
886      Calc::Value(v) => Calc::Value(Box::new(*v * other)),
887      Calc::Number(n) => Calc::Number(n * other),
888      Calc::Sum(a, b) => Calc::Sum(Box::new(*a * other), Box::new(*b * other)),
889      Calc::Product(num, calc) => {
890        let num = num * other;
891        if num == 1.0 {
892          return *calc;
893        }
894        Calc::Product(num, calc)
895      }
896      Calc::Function(f) => match *f {
897        MathFunction::Calc(c) => Calc::Function(Box::new(MathFunction::Calc(c * other))),
898        _ => Calc::Product(other, Box::new(Calc::Function(f))),
899      },
900    }
901  }
902}
903
904impl<V: AddInternal + std::convert::Into<Calc<V>> + std::convert::From<Calc<V>> + std::fmt::Debug> AddInternal
905  for Calc<V>
906{
907  fn add(self, other: Calc<V>) -> Calc<V> {
908    match (self, other) {
909      (Calc::Value(a), Calc::Value(b)) => (a.add(*b)).into(),
910      (Calc::Number(a), Calc::Number(b)) => Calc::Number(a + b),
911      (Calc::Value(a), b) => (a.add(V::from(b))).into(),
912      (a, Calc::Value(b)) => (V::from(a).add(*b)).into(),
913      (Calc::Function(a), b) => Calc::Sum(Box::new(Calc::Function(a)), Box::new(b)),
914      (a, Calc::Function(b)) => Calc::Sum(Box::new(a), Box::new(Calc::Function(b))),
915      (a, b) => V::from(a).add(V::from(b)).into(),
916    }
917  }
918}
919
920impl<V: ToCss + std::ops::Mul<f32, Output = V> + TrySign + Clone + std::fmt::Debug> ToCss for Calc<V> {
921  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
922  where
923    W: std::fmt::Write,
924  {
925    let was_in_calc = dest.in_calc;
926    dest.in_calc = true;
927
928    let res = match self {
929      Calc::Value(v) => v.to_css(dest),
930      Calc::Number(n) => n.to_css(dest),
931      Calc::Sum(a, b) => {
932        a.to_css(dest)?;
933        // Whitespace is always required.
934        let b = &**b;
935        if b.is_sign_negative() {
936          dest.write_str(" - ")?;
937          let b = b.clone() * -1.0;
938          b.to_css(dest)
939        } else {
940          dest.write_str(" + ")?;
941          b.to_css(dest)
942        }
943      }
944      Calc::Product(num, calc) => {
945        if num.abs() < 1.0 {
946          let div = 1.0 / num;
947          calc.to_css(dest)?;
948          dest.delim('/', true)?;
949          div.to_css(dest)
950        } else {
951          num.to_css(dest)?;
952          dest.delim('*', true)?;
953          calc.to_css(dest)
954        }
955      }
956      Calc::Function(f) => f.to_css(dest),
957    };
958
959    dest.in_calc = was_in_calc;
960    res
961  }
962}
963
964impl<V: TrySign> TrySign for Calc<V> {
965  fn try_sign(&self) -> Option<f32> {
966    match self {
967      Calc::Number(v) => v.try_sign(),
968      Calc::Value(v) => v.try_sign(),
969      _ => None,
970    }
971  }
972}