style: Implement CSS exponential functions

Differential Revision: https://phabricator.services.mozilla.com/D170842
This commit is contained in:
Em Zhan 2023-03-05 10:42:36 +00:00 committed by Martin Robinson
parent 0b20b343e6
commit 21d1bdeb9b
2 changed files with 147 additions and 40 deletions

View file

@ -192,6 +192,8 @@ pub enum GenericCalcNode<L> {
/// Is the function mod or rem?
op: ModRemOp,
},
/// A `hypot()` function
Hypot(crate::OwnedSlice<GenericCalcNode<L>>),
}
pub use self::GenericCalcNode as CalcNode;
@ -357,6 +359,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
op,
}
},
Self::Hypot(ref c) => CalcNode::Hypot(map_children(c, map)),
}
}
@ -535,6 +538,13 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
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<L: CalcNodeLeaf> CalcNode<L> {
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<L: CalcNodeLeaf> CalcNode<L> {
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<L: CalcNodeLeaf> CalcNode<L> {
*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<L: CalcNodeLeaf> CalcNode<L> {
true
},
Self::Hypot(_) => {
dest.write_str("hypot(")?;
true
},
_ => {
if is_outermost {
dest.write_str("calc(")?;
@ -1016,7 +1060,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
};
match *self {
Self::MinMax(ref children, _) => {
Self::MinMax(ref children, _) | Self::Hypot(ref children) => {
let mut first = true;
for child in &**children {
if !first {

View file

@ -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<CSSFloat, ()> {
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<CSSFloat, ParseError<'i>> {
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<CSSFloat, ParseError<'i>> {
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<CSSFloat, ParseError<'i>>
where
F: FnOnce() -> Result<CSSFloat, ()>,
{
closure()
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
/// Tries to simplify this expression into a `<length>` or `<percentage>`
/// 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
};