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.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#[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 + 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 let cmp = match (&max, ¢er) {
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 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, ¢er) {
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 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 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 if let Some(v) = v.try_map(|s| s.sign()) {
526 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; }
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 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 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 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 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 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 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}