1use 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#[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 Calc(Calc<V>),
36 Min(Vec<Calc<V>>),
38 Max(Vec<Calc<V>>),
40 Clamp(Calc<V>, Calc<V>, Calc<V>),
42 Round(RoundingStrategy, Calc<V>, Calc<V>),
44 Rem(Calc<V>, Calc<V>),
46 Mod(Calc<V>, Calc<V>),
48 Abs(Calc<V>),
50 Sign(Calc<V>),
52 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 pub enum RoundingStrategy {
94 Nearest,
96 Up,
98 Down,
100 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 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#[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 Value(Box<V>),
252 Number(CSSNumber),
254 #[cfg_attr(feature = "visitor", skip_type)]
256 Sum(Box<Calc<V>>, Box<Calc<V>>),
257 #[cfg_attr(feature = "visitor", skip_type)]
259 Product(CSSNumber, Box<Calc<V>>),
260 #[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 pub enum Constant {
280 "e": E,
282 "pi": Pi,
284 "infinity": Infinity,
286 "-infinity": NegativeInfinity,
288 "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 let cmp = if let (Some(Calc::Value(max_val)), Calc::Value(center_val)) = (&max, ¢er) {
386 center_val.partial_cmp(&max_val)
387 } else {
388 None
389 };
390
391 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, ¢er) {
404 center_val.partial_cmp(&min_val)
405 } else {
406 None
407 };
408
409 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 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 if let Some(v) = v.try_map(|s| s.sign()) {
522 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; }
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 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 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 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 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 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 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}