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.current, 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      + TryFrom<Calc<V>>
318      + TryFrom<Angle>
319      + TryInto<Angle>
320      + Clone
321      + std::fmt::Debug,
322  > Parse<'i> for Calc<V>
323{
324  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
325    Self::parse_with(input, |_| None)
326  }
327}
328
329impl<
330    'i,
331    V: Parse<'i>
332      + std::ops::Mul<f32, Output = V>
333      + AddInternal
334      + TryOp
335      + TryMap
336      + TrySign
337      + std::cmp::PartialOrd<V>
338      + Into<Calc<V>>
339      + TryFrom<Calc<V>>
340      + TryFrom<Angle>
341      + TryInto<Angle>
342      + Clone
343      + std::fmt::Debug,
344  > Calc<V>
345{
346  pub(crate) fn parse_with<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
347    input: &mut Parser<'i, 't>,
348    parse_ident: Parse,
349  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
350    let location = input.current_source_location();
351    let f = input.expect_function()?;
352    match_ignore_ascii_case! { &f,
353      "calc" => {
354        let calc = input.parse_nested_block(|input| Calc::parse_sum(input, parse_ident))?;
355        match calc {
356          Calc::Value(_) | Calc::Number(_) => Ok(calc),
357          _ => Ok(Calc::Function(Box::new(MathFunction::Calc(calc))))
358        }
359      },
360      "min" => {
361        let mut args = input.parse_nested_block(|input| input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident)))?;
362        let mut reduced = Calc::reduce_args(&mut args, std::cmp::Ordering::Less);
363        if reduced.len() == 1 {
364          return Ok(reduced.remove(0))
365        }
366        Ok(Calc::Function(Box::new(MathFunction::Min(reduced))))
367      },
368      "max" => {
369        let mut args = input.parse_nested_block(|input| input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident)))?;
370        let mut reduced = Calc::reduce_args(&mut args, std::cmp::Ordering::Greater);
371        if reduced.len() == 1 {
372          return Ok(reduced.remove(0))
373        }
374        Ok(Calc::Function(Box::new(MathFunction::Max(reduced))))
375      },
376      "clamp" => {
377        let (mut min, mut center, mut max) = input.parse_nested_block(|input| {
378          let min = Some(Calc::parse_sum(input, parse_ident)?);
379          input.expect_comma()?;
380          let center: Calc<V> = Calc::parse_sum(input, parse_ident)?;
381          input.expect_comma()?;
382          let max = Some(Calc::parse_sum(input, parse_ident)?);
383          Ok((min, center, max))
384        })?;
385
386        // According to the spec, the minimum should "win" over the maximum if they are in the wrong order.
387        let cmp = match (&max, &center) {
388          (Some(Calc::Value(max_val)), Calc::Value(center_val)) => center_val.partial_cmp(&max_val),
389          (Some(Calc::Number(max_val)), Calc::Number(center_val)) => center_val.partial_cmp(max_val),
390          _ => None,
391        };
392
393        // If center is known to be greater than the maximum, replace it with maximum and remove the max argument.
394        // Otherwise, if center is known to be less than the maximum, remove the max argument.
395        match cmp {
396          Some(std::cmp::Ordering::Greater) => {
397            center = std::mem::take(&mut max).unwrap();
398          }
399          Some(_) => {
400            max = None;
401          }
402          None => {}
403        }
404
405        if cmp.is_some() {
406          let cmp = match (&min, &center) {
407            (Some(Calc::Value(min_val)), Calc::Value(center_val)) => center_val.partial_cmp(&min_val),
408            (Some(Calc::Number(min_val)), Calc::Number(center_val)) => center_val.partial_cmp(min_val),
409            _ => None,
410          };
411
412          // If center is known to be less than the minimum, replace it with minimum and remove the min argument.
413          // Otherwise, if center is known to be greater than the minimum, remove the min argument.
414          match cmp {
415            Some(std::cmp::Ordering::Less) => {
416              center = std::mem::take(&mut min).unwrap();
417            }
418            Some(_) => {
419              min = None;
420            }
421            None => {}
422          }
423        }
424
425        // Generate clamp(), min(), max(), or value depending on which arguments are left.
426        match (min, max) {
427          (None, None) => Ok(center),
428          (Some(min), None) => Ok(Calc::Function(Box::new(MathFunction::Max(vec![min, center])))),
429          (None, Some(max)) => Ok(Calc::Function(Box::new(MathFunction::Min(vec![center, max])))),
430          (Some(min), Some(max)) => Ok(Calc::Function(Box::new(MathFunction::Clamp(min, center, max))))
431        }
432      },
433      "round" => {
434        input.parse_nested_block(|input| {
435          let strategy = if let Ok(s) = input.try_parse(RoundingStrategy::parse) {
436            input.expect_comma()?;
437            s
438          } else {
439            RoundingStrategy::default()
440          };
441
442          Self::parse_math_fn(
443            input,
444            |a, b| round(a, b, strategy),
445            |a, b| MathFunction::Round(strategy, a, b),
446            parse_ident
447          )
448        })
449      },
450      "rem" => {
451        input.parse_nested_block(|input| {
452          Self::parse_math_fn(input, std::ops::Rem::rem, MathFunction::Rem, parse_ident)
453        })
454      },
455      "mod" => {
456        input.parse_nested_block(|input| {
457          Self::parse_math_fn(input, modulo, MathFunction::Mod, parse_ident)
458        })
459      },
460      "sin" => Self::parse_trig(input, f32::sin, false, parse_ident),
461      "cos" => Self::parse_trig(input, f32::cos, false, parse_ident),
462      "tan" => Self::parse_trig(input, f32::tan, false, parse_ident),
463      "asin" => Self::parse_trig(input, f32::asin, true, parse_ident),
464      "acos" => Self::parse_trig(input, f32::acos, true, parse_ident),
465      "atan" => Self::parse_trig(input, f32::atan, true, parse_ident),
466      "atan2" => {
467        input.parse_nested_block(|input| {
468          let res = Self::parse_atan2(input, parse_ident)?;
469          if let Ok(v) = V::try_from(res) {
470            return Ok(Calc::Value(Box::new(v)))
471          }
472
473          Err(input.new_custom_error(ParserError::InvalidValue))
474        })
475      },
476      "pow" => {
477        input.parse_nested_block(|input| {
478          let a = Self::parse_numeric(input, parse_ident)?;
479          input.expect_comma()?;
480          let b = Self::parse_numeric(input, parse_ident)?;
481          Ok(Calc::Number(a.powf(b)))
482        })
483      },
484      "log" => {
485        input.parse_nested_block(|input| {
486          let value = Self::parse_numeric(input, parse_ident)?;
487          if input.try_parse(|input| input.expect_comma()).is_ok() {
488            let base = Self::parse_numeric(input, parse_ident)?;
489            Ok(Calc::Number(value.log(base)))
490          } else {
491            Ok(Calc::Number(value.ln()))
492          }
493        })
494      },
495      "sqrt" => Self::parse_numeric_fn(input, f32::sqrt, parse_ident),
496      "exp" => Self::parse_numeric_fn(input, f32::exp, parse_ident),
497      "hypot" => {
498        input.parse_nested_block(|input| {
499          let args: Vec<Self> = input.parse_comma_separated(|input| Calc::parse_sum(input, parse_ident))?;
500          Self::parse_hypot(&args)?
501            .map_or_else(
502              || Ok(Calc::Function(Box::new(MathFunction::Hypot(args)))),
503              |v| Ok(v)
504            )
505        })
506      },
507      "abs" => {
508        input.parse_nested_block(|input| {
509          let v: Calc<V> = Self::parse_sum(input, parse_ident)?;
510          Self::apply_map(&v, f32::abs)
511            .map_or_else(
512              || Ok(Calc::Function(Box::new(MathFunction::Abs(v)))),
513              |v| Ok(v)
514            )
515        })
516      },
517      "sign" => {
518        input.parse_nested_block(|input| {
519          let v: Calc<V> = Self::parse_sum(input, parse_ident)?;
520          match &v {
521            Calc::Number(n) => return Ok(Calc::Number(n.sign())),
522            Calc::Value(v) => {
523              // First map so we ignore percentages, which must be resolved to their
524              // computed value in order to determine the sign.
525              if let Some(v) = v.try_map(|s| s.sign()) {
526                // sign() always resolves to a number.
527                return Ok(Calc::Number(v.try_sign().unwrap()));
528              }
529            }
530            _ => {}
531          }
532
533          Ok(Calc::Function(Box::new(MathFunction::Sign(v))))
534        })
535      },
536       _ => Err(location.new_unexpected_token_error(Token::Ident(f.clone()))),
537    }
538  }
539
540  fn parse_sum<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
541    input: &mut Parser<'i, 't>,
542    parse_ident: Parse,
543  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
544    let mut cur: Calc<V> = Calc::parse_product(input, parse_ident)?;
545    loop {
546      let start = input.state();
547      match input.next_including_whitespace() {
548        Ok(&Token::WhiteSpace(_)) => {
549          if input.is_exhausted() {
550            break; // allow trailing whitespace
551          }
552          match *input.next()? {
553            Token::Delim('+') => {
554              let next = Calc::parse_product(input, parse_ident)?;
555              cur = cur.add(next).map_err(|_| input.new_custom_error(ParserError::InvalidValue))?;
556            }
557            Token::Delim('-') => {
558              let mut rhs = Calc::parse_product(input, parse_ident)?;
559              rhs = rhs * -1.0;
560              cur = cur.add(rhs).map_err(|_| input.new_custom_error(ParserError::InvalidValue))?;
561            }
562            ref t => {
563              let t = t.clone();
564              return Err(input.new_unexpected_token_error(t));
565            }
566          }
567        }
568        _ => {
569          input.reset(&start);
570          break;
571        }
572      }
573    }
574    Ok(cur)
575  }
576
577  fn parse_product<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
578    input: &mut Parser<'i, 't>,
579    parse_ident: Parse,
580  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
581    let mut node = Calc::parse_value(input, parse_ident)?;
582    loop {
583      let start = input.state();
584      match input.next() {
585        Ok(&Token::Delim('*')) => {
586          // At least one of the operands must be a number.
587          let rhs = Self::parse_value(input, parse_ident)?;
588          if let Calc::Number(val) = rhs {
589            node = node * val;
590          } else if let Calc::Number(val) = node {
591            node = rhs;
592            node = node * val;
593          } else {
594            return Err(input.new_unexpected_token_error(Token::Delim('*')));
595          }
596        }
597        Ok(&Token::Delim('/')) => {
598          let rhs = Self::parse_value(input, parse_ident)?;
599          if let Calc::Number(val) = rhs {
600            if val != 0.0 {
601              node = node * (1.0 / val);
602              continue;
603            }
604          }
605          return Err(input.new_custom_error(ParserError::InvalidValue));
606        }
607        _ => {
608          input.reset(&start);
609          break;
610        }
611      }
612    }
613    Ok(node)
614  }
615
616  fn parse_value<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
617    input: &mut Parser<'i, 't>,
618    parse_ident: Parse,
619  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
620    // Parse nested calc() and other math functions.
621    if let Ok(calc) = input.try_parse(Self::parse) {
622      match calc {
623        Calc::Function(f) => {
624          return Ok(match *f {
625            MathFunction::Calc(c) => c,
626            _ => Calc::Function(f),
627          })
628        }
629        c => return Ok(c),
630      }
631    }
632
633    if input.try_parse(|input| input.expect_parenthesis_block()).is_ok() {
634      return input.parse_nested_block(|input| Calc::parse_sum(input, parse_ident));
635    }
636
637    if let Ok(num) = input.try_parse(|input| input.expect_number()) {
638      return Ok(Calc::Number(num));
639    }
640
641    if let Ok(constant) = input.try_parse(Constant::parse) {
642      return Ok(Calc::Number(constant.into()));
643    }
644
645    let location = input.current_source_location();
646    if let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
647      if let Some(v) = parse_ident(ident.as_ref()) {
648        return Ok(v);
649      }
650
651      return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));
652    }
653
654    let value = input.try_parse(V::parse)?;
655    Ok(Calc::Value(Box::new(value)))
656  }
657
658  fn reduce_args(args: &mut Vec<Calc<V>>, cmp: std::cmp::Ordering) -> Vec<Calc<V>> {
659    // Reduces the arguments of a min() or max() expression, combining compatible values.
660    // e.g. min(1px, 1em, 2px, 3in) => min(1px, 1em)
661    // Also handles plain numbers: min(1, 2, 3) => min(1, 2)
662    let mut reduced: Vec<Calc<V>> = vec![];
663    for arg in args.drain(..) {
664      let mut found = None;
665      match &arg {
666        Calc::Value(val) => {
667          for b in reduced.iter_mut() {
668            if let Calc::Value(v) = b {
669              match val.partial_cmp(v) {
670                Some(ord) if ord == cmp => {
671                  found = Some(Some(b));
672                  break;
673                }
674                Some(_) => {
675                  found = Some(None);
676                  break;
677                }
678                None => {}
679              }
680            }
681          }
682        }
683        Calc::Number(val) => {
684          for b in reduced.iter_mut() {
685            if let Calc::Number(v) = b {
686              match val.partial_cmp(v) {
687                Some(ord) if ord == cmp => {
688                  found = Some(Some(b));
689                  break;
690                }
691                Some(_) => {
692                  found = Some(None);
693                  break;
694                }
695                None => {}
696              }
697            }
698          }
699        }
700        _ => {}
701      }
702      if let Some(r) = found {
703        if let Some(r) = r {
704          *r = arg
705        }
706      } else {
707        reduced.push(arg)
708      }
709    }
710    reduced
711  }
712
713  fn parse_math_fn<
714    't,
715    O: FnOnce(f32, f32) -> f32,
716    F: FnOnce(Calc<V>, Calc<V>) -> MathFunction<V>,
717    Parse: Copy + Fn(&str) -> Option<Calc<V>>,
718  >(
719    input: &mut Parser<'i, 't>,
720    op: O,
721    fallback: F,
722    parse_ident: Parse,
723  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
724    let a: Calc<V> = Calc::parse_sum(input, parse_ident)?;
725    input.expect_comma()?;
726    let b: Calc<V> = Calc::parse_sum(input, parse_ident)?;
727
728    Ok(Self::apply_op(&a, &b, op).unwrap_or_else(|| Calc::Function(Box::new(fallback(a, b)))))
729  }
730
731  fn apply_op<'t, O: FnOnce(f32, f32) -> f32>(a: &Calc<V>, b: &Calc<V>, op: O) -> Option<Self> {
732    match (a, b) {
733      (Calc::Value(a), Calc::Value(b)) => {
734        if let Some(v) = a.try_op(&**b, op) {
735          return Some(Calc::Value(Box::new(v)));
736        }
737      }
738      (Calc::Number(a), Calc::Number(b)) => return Some(Calc::Number(op(*a, *b))),
739      _ => {}
740    }
741
742    None
743  }
744
745  fn apply_map<'t, O: FnOnce(f32) -> f32>(v: &Calc<V>, op: O) -> Option<Self> {
746    match v {
747      Calc::Number(n) => return Some(Calc::Number(op(*n))),
748      Calc::Value(v) => {
749        if let Some(v) = v.try_map(op) {
750          return Some(Calc::Value(Box::new(v)));
751        }
752      }
753      _ => {}
754    }
755
756    None
757  }
758
759  fn parse_trig<'t, F: FnOnce(f32) -> f32, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
760    input: &mut Parser<'i, 't>,
761    f: F,
762    to_angle: bool,
763    parse_ident: Parse,
764  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
765    input.parse_nested_block(|input| {
766      let v: Calc<Angle> = Calc::parse_sum(input, |v| {
767        parse_ident(v).and_then(|v| -> Option<Calc<Angle>> {
768          match v {
769            Calc::Number(v) => Some(Calc::Number(v)),
770            Calc::Value(v) => (*v).try_into().ok().map(|v| Calc::Value(Box::new(v))),
771            _ => None,
772          }
773        })
774      })?;
775      let rad = match v {
776        Calc::Value(angle) if !to_angle => f(angle.to_radians()),
777        Calc::Number(v) => f(v),
778        _ => return Err(input.new_custom_error(ParserError::InvalidValue)),
779      };
780
781      if to_angle && !rad.is_nan() {
782        if let Ok(v) = V::try_from(Angle::Rad(rad)) {
783          return Ok(Calc::Value(Box::new(v)));
784        } else {
785          return Err(input.new_custom_error(ParserError::InvalidValue));
786        }
787      } else {
788        Ok(Calc::Number(rad))
789      }
790    })
791  }
792
793  fn parse_numeric<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
794    input: &mut Parser<'i, 't>,
795    parse_ident: Parse,
796  ) -> Result<f32, ParseError<'i, ParserError<'i>>> {
797    let v: Calc<CSSNumber> = Calc::parse_sum(input, |v| {
798      parse_ident(v).and_then(|v| match v {
799        Calc::Number(v) => Some(Calc::Number(v)),
800        _ => None,
801      })
802    })?;
803    match v {
804      Calc::Number(n) => Ok(n),
805      Calc::Value(v) => Ok(*v),
806      _ => Err(input.new_custom_error(ParserError::InvalidValue)),
807    }
808  }
809
810  fn parse_numeric_fn<'t, F: FnOnce(f32) -> f32, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
811    input: &mut Parser<'i, 't>,
812    f: F,
813    parse_ident: Parse,
814  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
815    input.parse_nested_block(|input| {
816      let v = Self::parse_numeric(input, parse_ident)?;
817      Ok(Calc::Number(f(v)))
818    })
819  }
820
821  fn parse_atan2<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
822    input: &mut Parser<'i, 't>,
823    parse_ident: Parse,
824  ) -> Result<Angle, ParseError<'i, ParserError<'i>>> {
825    // atan2 supports arguments of any <number>, <dimension>, or <percentage>, even ones that wouldn't
826    // normally be supported by V. The only requirement is that the arguments be of the same type.
827    // Try parsing with each type, and return the first one that parses successfully.
828    if let Ok(v) = input.try_parse(|input| Calc::<Length>::parse_atan2_args(input, |_| None)) {
829      return Ok(v);
830    }
831
832    if let Ok(v) = input.try_parse(|input| Calc::<Percentage>::parse_atan2_args(input, |_| None)) {
833      return Ok(v);
834    }
835
836    if let Ok(v) = input.try_parse(|input| Calc::<Angle>::parse_atan2_args(input, |_| None)) {
837      return Ok(v);
838    }
839
840    if let Ok(v) = input.try_parse(|input| Calc::<Time>::parse_atan2_args(input, |_| None)) {
841      return Ok(v);
842    }
843
844    Calc::<CSSNumber>::parse_atan2_args(input, |v| {
845      parse_ident(v).and_then(|v| match v {
846        Calc::Number(v) => Some(Calc::Number(v)),
847        _ => None,
848      })
849    })
850  }
851
852  fn parse_atan2_args<'t, Parse: Copy + Fn(&str) -> Option<Calc<V>>>(
853    input: &mut Parser<'i, 't>,
854    parse_ident: Parse,
855  ) -> Result<Angle, ParseError<'i, ParserError<'i>>> {
856    let a = Calc::<V>::parse_sum(input, parse_ident)?;
857    input.expect_comma()?;
858    let b = Calc::<V>::parse_sum(input, parse_ident)?;
859
860    match (&a, &b) {
861      (Calc::Value(a), Calc::Value(b)) => {
862        if let Some(v) = a.try_op_to(&**b, |a, b| Angle::Rad(a.atan2(b))) {
863          return Ok(v);
864        }
865      }
866      (Calc::Number(a), Calc::Number(b)) => return Ok(Angle::Rad(a.atan2(*b))),
867      _ => {}
868    }
869
870    // We don't have a way to represent arguments that aren't angles, so just error.
871    // This will fall back to an unparsed property, leaving the atan2() function intact.
872    Err(input.new_custom_error(ParserError::InvalidValue))
873  }
874
875  fn parse_hypot<'t>(args: &Vec<Self>) -> Result<Option<Self>, ParseError<'i, ParserError<'i>>> {
876    if args.len() == 1 {
877      return Ok(Some(args[0].clone()));
878    }
879
880    if args.len() == 2 {
881      return Ok(Self::apply_op(&args[0], &args[1], f32::hypot));
882    }
883
884    let mut iter = args.iter();
885    let first = match Self::apply_map(&iter.next().unwrap(), |v| v.powi(2)) {
886      Some(v) => v,
887      None => return Ok(None),
888    };
889    let sum = iter.try_fold(first, |acc, arg| {
890      Self::apply_op(&acc, &arg, |a, b| a + b.powi(2)).map_or_else(|| Err(()), |v| Ok(v))
891    });
892
893    let sum = match sum {
894      Ok(s) => s,
895      Err(_) => return Ok(None),
896    };
897
898    Ok(Self::apply_map(&sum, f32::sqrt))
899  }
900}
901
902impl<V: std::ops::Mul<f32, Output = V>> std::ops::Mul<f32> for Calc<V> {
903  type Output = Self;
904
905  fn mul(self, other: f32) -> Self {
906    if other == 1.0 {
907      return self;
908    }
909
910    match self {
911      Calc::Value(v) => Calc::Value(Box::new(*v * other)),
912      Calc::Number(n) => Calc::Number(n * other),
913      Calc::Sum(a, b) => Calc::Sum(Box::new(*a * other), Box::new(*b * other)),
914      Calc::Product(num, calc) => {
915        let num = num * other;
916        if num == 1.0 {
917          return *calc;
918        }
919        Calc::Product(num, calc)
920      }
921      Calc::Function(f) => match *f {
922        MathFunction::Calc(c) => Calc::Function(Box::new(MathFunction::Calc(c * other))),
923        _ => Calc::Product(other, Box::new(Calc::Function(f))),
924      },
925    }
926  }
927}
928
929impl<V: AddInternal + std::convert::Into<Calc<V>> + std::convert::TryFrom<Calc<V>> + std::fmt::Debug> Calc<V> {
930  pub(crate) fn add(self, other: Calc<V>) -> Result<Calc<V>, <V as TryFrom<Calc<V>>>::Error> {
931    Ok(match (self, other) {
932      (Calc::Value(a), Calc::Value(b)) => (a.add(*b)).into(),
933      (Calc::Number(a), Calc::Number(b)) => Calc::Number(a + b),
934      (Calc::Sum(a, b), Calc::Number(c)) => {
935        if let Calc::Number(a) = *a {
936          Calc::Sum(Box::new(Calc::Number(a + c)), b)
937        } else if let Calc::Number(b) = *b {
938          Calc::Sum(a, Box::new(Calc::Number(b + c)))
939        } else {
940          Calc::Sum(Box::new(Calc::Sum(a, b)), Box::new(Calc::Number(c)))
941        }
942      }
943      (Calc::Number(a), Calc::Sum(b, c)) => {
944        if let Calc::Number(b) = *b {
945          Calc::Sum(Box::new(Calc::Number(a + b)), c)
946        } else if let Calc::Number(c) = *c {
947          Calc::Sum(Box::new(Calc::Number(a + c)), b)
948        } else {
949          Calc::Sum(Box::new(Calc::Number(a)), Box::new(Calc::Sum(b, c)))
950        }
951      }
952      (a @ Calc::Number(_), b)
953      | (a, b @ Calc::Number(_))
954      | (a @ Calc::Product(..), b)
955      | (a, b @ Calc::Product(..)) => Calc::Sum(Box::new(a), Box::new(b)),
956      (Calc::Function(a), b) => Calc::Sum(Box::new(Calc::Function(a)), Box::new(b)),
957      (a, Calc::Function(b)) => Calc::Sum(Box::new(a), Box::new(Calc::Function(b))),
958      (Calc::Value(a), b) => (a.add(V::try_from(b)?)).into(),
959      (a, Calc::Value(b)) => (V::try_from(a)?.add(*b)).into(),
960      (a @ Calc::Sum(..), b @ Calc::Sum(..)) => V::try_from(a)?.add(V::try_from(b)?).into(),
961    })
962  }
963}
964
965impl<V: ToCss + std::ops::Mul<f32, Output = V> + TrySign + Clone + std::fmt::Debug> ToCss for Calc<V> {
966  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
967  where
968    W: std::fmt::Write,
969  {
970    let was_in_calc = dest.in_calc;
971    dest.in_calc = true;
972
973    let res = match self {
974      Calc::Value(v) => v.to_css(dest),
975      Calc::Number(n) => n.to_css(dest),
976      Calc::Sum(a, b) => {
977        a.to_css(dest)?;
978        // Whitespace is always required.
979        let b = &**b;
980        if b.is_sign_negative() {
981          dest.write_str(" - ")?;
982          let b = b.clone() * -1.0;
983          b.to_css(dest)
984        } else {
985          dest.write_str(" + ")?;
986          b.to_css(dest)
987        }
988      }
989      Calc::Product(num, calc) => {
990        if num.abs() < 1.0 {
991          let div = 1.0 / num;
992          calc.to_css(dest)?;
993          dest.delim('/', true)?;
994          div.to_css(dest)
995        } else {
996          num.to_css(dest)?;
997          dest.delim('*', true)?;
998          calc.to_css(dest)
999        }
1000      }
1001      Calc::Function(f) => f.to_css(dest),
1002    };
1003
1004    dest.in_calc = was_in_calc;
1005    res
1006  }
1007}
1008
1009impl<V: TrySign> TrySign for Calc<V> {
1010  fn try_sign(&self) -> Option<f32> {
1011    match self {
1012      Calc::Number(v) => v.try_sign(),
1013      Calc::Value(v) => v.try_sign(),
1014      Calc::Product(c, v) => v.try_sign().map(|s| s * c.sign()),
1015      Calc::Function(f) => f.try_sign(),
1016      _ => None,
1017    }
1018  }
1019}
1020
1021impl<V: TrySign> TrySign for MathFunction<V> {
1022  fn try_sign(&self) -> Option<f32> {
1023    match self {
1024      MathFunction::Abs(_) => Some(1.0),
1025      MathFunction::Max(values) | MathFunction::Min(values) => {
1026        let mut iter = values.iter();
1027        if let Some(sign) = iter.next().and_then(|f| f.try_sign()) {
1028          for value in iter {
1029            if let Some(s) = value.try_sign() {
1030              if s != sign {
1031                return None;
1032              }
1033            } else {
1034              return None;
1035            }
1036          }
1037          return Some(sign);
1038        } else {
1039          return None;
1040        }
1041      }
1042      MathFunction::Clamp(a, b, c) => {
1043        if let (Some(a), Some(b), Some(c)) = (a.try_sign(), b.try_sign(), c.try_sign()) {
1044          if a == b && b == c {
1045            return Some(a);
1046          }
1047        }
1048        return None;
1049      }
1050      MathFunction::Round(_, a, b) => {
1051        if let (Some(a), Some(b)) = (a.try_sign(), b.try_sign()) {
1052          if a == b {
1053            return Some(a);
1054          }
1055        }
1056        return None;
1057      }
1058      MathFunction::Sign(v) => v.try_sign(),
1059      MathFunction::Calc(v) => v.try_sign(),
1060      _ => None,
1061    }
1062  }
1063}