style: Add experimental support for "e", "pi", and various trigonometric functions in calc()

I'll add some tests before enabling. Also, WebKit folks (who have
implemented cos() / tan() / sin()) said they will upstream their tests
to WPT, so I'll extend those with the inverse functions before landing
as well.

Differential Revision: https://phabricator.services.mozilla.com/D124990
This commit is contained in:
Emilio Cobos Álvarez 2023-05-27 07:12:22 +02:00 committed by Oriol Brufau
parent 4f193fbf49
commit 4522e7f94a
2 changed files with 93 additions and 8 deletions

View file

@ -130,6 +130,15 @@ impl Angle {
}
}
/// Creates an angle with the given value in radians.
#[inline]
pub fn from_radians(value: CSSFloat) -> Self {
Angle {
value: AngleDimension::Rad(value),
was_calc: false,
}
}
/// Return `0deg`.
pub fn zero() -> Self {
Self::from_degrees(0.0, false)
@ -141,6 +150,13 @@ impl Angle {
self.value.degrees()
}
/// Returns the value of the angle in radians.
#[inline]
pub fn radians(&self) -> CSSFloat {
const RAD_PER_DEG: f32 = PI / 180.0;
self.value.degrees() * RAD_PER_DEG
}
/// Whether this specified angle came from a `calc()` expression.
#[inline]
pub fn was_calc(&self) -> bool {

View file

@ -21,7 +21,7 @@ use style_traits::values::specified::AllowedNumericType;
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
/// The name of the mathematical function that we're parsing.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Parse)]
pub enum MathFunction {
/// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
Calc,
@ -31,6 +31,18 @@ pub enum MathFunction {
Max,
/// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
Clamp,
/// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin
Sin,
/// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos
Cos,
/// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan
Tan,
/// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin
Asin,
/// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos
Acos,
/// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan
Atan,
}
/// A leaf node inside a `Calc` expression's AST.
@ -301,6 +313,17 @@ impl CalcNode {
let function = CalcNode::math_function(name, location)?;
CalcNode::parse(context, input, function, expected_unit)
},
(&Token::Ident(ref ident), _) => {
if !static_prefs::pref!("layout.css.trig.enabled") {
return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));
}
let number = match_ignore_ascii_case! { &**ident,
"e" => std::f32::consts::E,
"pi" => std::f32::consts::PI,
_ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))),
};
Ok(CalcNode::Leaf(Leaf::Number(number)))
},
(t, _) => Err(location.new_unexpected_token_error(t.clone())),
}
}
@ -350,6 +373,47 @@ impl CalcNode {
Ok(Self::MinMax(arguments.into(), op))
},
MathFunction::Sin |
MathFunction::Cos |
MathFunction::Tan => {
let argument = Self::parse_argument(context, input, CalcUnit::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 number = match function {
MathFunction::Sin => radians.sin(),
MathFunction::Cos => radians.cos(),
MathFunction::Tan => radians.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, CalcUnit::Number)?;
let number = match argument.to_number() {
Ok(v) => v,
Err(()) => return Err(
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
),
};
let radians = match function {
MathFunction::Asin => number.asin(),
MathFunction::Acos => number.acos(),
MathFunction::Atan => number.atan(),
_ => unsafe { debug_unreachable!("We just checked!"); },
};
Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians))))
},
}
})
}
@ -522,13 +586,18 @@ impl CalcNode {
name: &CowRcStr<'i>,
location: cssparser::SourceLocation,
) -> Result<MathFunction, ParseError<'i>> {
Ok(match_ignore_ascii_case! { &*name,
"calc" => MathFunction::Calc,
"min" => MathFunction::Min,
"max" => MathFunction::Max,
"clamp" => MathFunction::Clamp,
_ => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))),
})
use self::MathFunction::*;
let function = match MathFunction::from_ident(&*name) {
Ok(f) => f,
Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))),
};
if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !static_prefs::pref!("layout.css.trig.enabled") {
return Err(location.new_unexpected_token_error(Token::Function(name.clone())));
}
Ok(function)
}
/// Convenience parsing function for integers.