diff --git a/components/style/values/generics/calc.rs b/components/style/values/generics/calc.rs index 9175a0debf6..a9d8f24f865 100644 --- a/components/style/values/generics/calc.rs +++ b/components/style/values/generics/calc.rs @@ -192,6 +192,8 @@ pub enum GenericCalcNode { /// Is the function mod or rem? op: ModRemOp, }, + /// A `hypot()` function + Hypot(crate::OwnedSlice>), } pub use self::GenericCalcNode as CalcNode; @@ -357,6 +359,7 @@ impl CalcNode { op, } }, + Self::Hypot(ref c) => CalcNode::Hypot(map_children(c, map)), } } @@ -535,6 +538,13 @@ impl CalcNode { ModRemOp::Rem => dividend - divisor * (dividend / divisor).trunc(), } }, + Self::Hypot(ref c) => { + let mut result: O = Zero::zero(); + for child in &**c { + result = result + child.resolve_internal(leaf_to_output_fn)?.powi(2); + } + result.sqrt() + } }) } @@ -613,6 +623,12 @@ impl CalcNode { dividend.mul_by(scalar); divisor.mul_by(scalar); }, + // Not possible to handle negatives in this case, see: https://bugzil.la/1815448 + Self::Hypot(ref mut children) => { + for node in &mut **children { + node.mul_by(scalar); + } + }, } } @@ -652,7 +668,7 @@ impl CalcNode { dividend.visit_depth_first_internal(f); divisor.visit_depth_first_internal(f); }, - Self::Sum(ref mut children) | Self::MinMax(ref mut children, _) => { + Self::Sum(ref mut children) | Self::MinMax(ref mut children, _) | Self::Hypot(ref mut children) => { for child in &mut **children { child.visit_depth_first_internal(f); } @@ -962,6 +978,30 @@ impl CalcNode { *children_slot = children.into_boxed_slice().into(); } }, + Self::Hypot(ref children) => { + let mut result = match children[0].try_op(&children[0], Mul::mul) { + Ok(res) => res, + Err(..) => return, + }; + + for child in children.iter().skip(1) { + let square = match child.try_op(&child, Mul::mul) { + Ok(res) => res, + Err(..) => return, + }; + result = match result.try_op(&square, Add::add) { + Ok(res) => res, + Err(..) => return, + } + } + + result = match result.try_op(&result, |a, _| a.sqrt()) { + Ok(res) => res, + Err(..) => return, + }; + + replace_self_with!(&mut result); + } Self::Leaf(ref mut l) => { l.simplify(); }, @@ -1007,6 +1047,10 @@ impl CalcNode { true }, + Self::Hypot(_) => { + dest.write_str("hypot(")?; + true + }, _ => { if is_outermost { dest.write_str("calc(")?; @@ -1016,7 +1060,7 @@ impl CalcNode { }; match *self { - Self::MinMax(ref children, _) => { + Self::MinMax(ref children, _) | Self::Hypot(ref children) => { let mut first = true; for child in &**children { if !first { diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index 75c6bc55211..04d9d7113dd 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -79,6 +79,16 @@ pub enum MathFunction { Atan, /// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2 Atan2, + /// `pow()`: https://drafts.csswg.org/css-values-4/#funcdef-pow + Pow, + /// `sqrt()`: https://drafts.csswg.org/css-values-4/#funcdef-sqrt + Sqrt, + /// `hypot()`: https://drafts.csswg.org/css-values-4/#funcdef-hypot + Hypot, + /// `log()`: https://drafts.csswg.org/css-values-4/#funcdef-log + Log, + /// `exp()`: https://drafts.csswg.org/css-values-4/#funcdef-exp + Exp, } /// A leaf node inside a `Calc` expression's AST. @@ -536,43 +546,26 @@ impl CalcNode { Ok(Self::MinMax(arguments.into(), op)) }, MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => { - let argument = Self::parse_argument(context, input, CalcUnits::ANGLE)?; - let radians = match argument.to_number() { - Ok(v) => v, - Err(()) => match argument.to_angle() { - Ok(angle) => angle.radians(), - Err(()) => { - return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ) - }, - }, - }; + let a = Self::parse_angle_argument(context, input)?; + let number = match function { - MathFunction::Sin => radians.sin(), - MathFunction::Cos => radians.cos(), - MathFunction::Tan => radians.tan(), + MathFunction::Sin => a.sin(), + MathFunction::Cos => a.cos(), + MathFunction::Tan => a.tan(), _ => unsafe { debug_unreachable!("We just checked!"); }, }; + Ok(Self::Leaf(Leaf::Number(number))) }, MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => { - let argument = Self::parse_argument(context, input, CalcUnits::empty())?; - let number = match argument.to_number() { - Ok(v) => v, - Err(()) => { - return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ) - }, - }; + let a = Self::parse_number_argument(context, input)?; let radians = match function { - MathFunction::Asin => number.asin(), - MathFunction::Acos => number.acos(), - MathFunction::Atan => number.atan(), + MathFunction::Asin => a.asin(), + MathFunction::Acos => a.acos(), + MathFunction::Atan => a.atan(), _ => unsafe { debug_unreachable!("We just checked!"); }, @@ -584,7 +577,8 @@ impl CalcNode { let a = Self::parse_argument(context, input, CalcUnits::ALL)?; input.expect_comma()?; let b = Self::parse_argument(context, input, CalcUnits::ALL)?; - fn resolve_atan2(a: CalcNode, b: CalcNode) -> Result { + + let radians = Self::try_resolve(input, || { if let Ok(a) = a.to_number() { let b = b.to_number()?; return Ok(a.atan2(b)); @@ -608,24 +602,80 @@ impl CalcNode { let a = a.into_length_or_percentage(AllowedNumericType::All)?; let b = b.into_length_or_percentage(AllowedNumericType::All)?; let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?; - return Ok(a.atan2(b)); - } - let radians = match resolve_atan2(a, b) { - Ok(v) => v, - Err(()) => { - return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ) - }, - }; + Ok(a.atan2(b)) + })?; Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) }, + MathFunction::Pow => { + let a = Self::parse_number_argument(context, input)?; + input.expect_comma()?; + let b = Self::parse_number_argument(context, input)?; + + let number = a.powf(b); + + Ok(Self::Leaf(Leaf::Number(number))) + }, + MathFunction::Sqrt => { + let a = Self::parse_number_argument(context, input)?; + + let number = a.sqrt(); + + Ok(Self::Leaf(Leaf::Number(number))) + }, + MathFunction::Hypot => { + let arguments = input.parse_comma_separated(|input| { + Self::parse_argument(context, input, allowed_units) + })?; + + Ok(Self::Hypot(arguments.into())) + }, + MathFunction::Log => { + let a = Self::parse_number_argument(context, input)?; + let b = input.try_parse(|input| { + input.expect_comma()?; + Self::parse_number_argument(context, input) + }).ok(); + + let number = match b { + Some(b) => a.log(b), + None => a.ln(), + }; + + Ok(Self::Leaf(Leaf::Number(number))) + }, + MathFunction::Exp => { + let a = Self::parse_number_argument(context, input)?; + + let number = a.exp(); + + Ok(Self::Leaf(Leaf::Number(number))) + }, } }) } + fn parse_angle_argument<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let argument = Self::parse_argument(context, input, CalcUnits::ANGLE)?; + argument + .to_number() + .or_else(|()| Ok(argument.to_angle()?.radians())) + .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + + fn parse_number_argument<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Self::parse_argument(context, input, CalcUnits::empty())? + .to_number() + .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + fn parse_argument<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, @@ -727,6 +777,17 @@ impl CalcNode { Ok(node) } + fn try_resolve<'i, 't, F>( + input: &Parser<'i, 't>, + closure: F, + ) -> Result> + where + F: FnOnce() -> Result, + { + closure() + .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + /// Tries to simplify this expression into a `` or `` /// value. fn into_length_or_percentage( @@ -809,6 +870,8 @@ impl CalcNode { round_enabled() } else if matches!(function, Mod | Rem) { mod_rem_enabled() + } else if matches!(function, Pow | Sqrt | Hypot | Log | Exp) { + static_prefs::pref!("layout.css.exp.enabled") } else { true };