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? /// Is the function mod or rem?
op: ModRemOp, op: ModRemOp,
}, },
/// A `hypot()` function
Hypot(crate::OwnedSlice<GenericCalcNode<L>>),
} }
pub use self::GenericCalcNode as CalcNode; pub use self::GenericCalcNode as CalcNode;
@ -357,6 +359,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
op, 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(), 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); dividend.mul_by(scalar);
divisor.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); dividend.visit_depth_first_internal(f);
divisor.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 { for child in &mut **children {
child.visit_depth_first_internal(f); child.visit_depth_first_internal(f);
} }
@ -962,6 +978,30 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
*children_slot = children.into_boxed_slice().into(); *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) => { Self::Leaf(ref mut l) => {
l.simplify(); l.simplify();
}, },
@ -1007,6 +1047,10 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
true true
}, },
Self::Hypot(_) => {
dest.write_str("hypot(")?;
true
},
_ => { _ => {
if is_outermost { if is_outermost {
dest.write_str("calc(")?; dest.write_str("calc(")?;
@ -1016,7 +1060,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
}; };
match *self { match *self {
Self::MinMax(ref children, _) => { Self::MinMax(ref children, _) | Self::Hypot(ref children) => {
let mut first = true; let mut first = true;
for child in &**children { for child in &**children {
if !first { if !first {

View file

@ -79,6 +79,16 @@ pub enum MathFunction {
Atan, Atan,
/// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2 /// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2
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. /// A leaf node inside a `Calc` expression's AST.
@ -536,43 +546,26 @@ impl CalcNode {
Ok(Self::MinMax(arguments.into(), op)) Ok(Self::MinMax(arguments.into(), op))
}, },
MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => { MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => {
let argument = Self::parse_argument(context, input, CalcUnits::ANGLE)?; let a = Self::parse_angle_argument(context, input)?;
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 { let number = match function {
MathFunction::Sin => radians.sin(), MathFunction::Sin => a.sin(),
MathFunction::Cos => radians.cos(), MathFunction::Cos => a.cos(),
MathFunction::Tan => radians.tan(), MathFunction::Tan => a.tan(),
_ => unsafe { _ => unsafe {
debug_unreachable!("We just checked!"); debug_unreachable!("We just checked!");
}, },
}; };
Ok(Self::Leaf(Leaf::Number(number))) Ok(Self::Leaf(Leaf::Number(number)))
}, },
MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => { MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => {
let argument = Self::parse_argument(context, input, CalcUnits::empty())?; let a = Self::parse_number_argument(context, input)?;
let number = match argument.to_number() {
Ok(v) => v,
Err(()) => {
return Err(
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
)
},
};
let radians = match function { let radians = match function {
MathFunction::Asin => number.asin(), MathFunction::Asin => a.asin(),
MathFunction::Acos => number.acos(), MathFunction::Acos => a.acos(),
MathFunction::Atan => number.atan(), MathFunction::Atan => a.atan(),
_ => unsafe { _ => unsafe {
debug_unreachable!("We just checked!"); debug_unreachable!("We just checked!");
}, },
@ -584,7 +577,8 @@ impl CalcNode {
let a = Self::parse_argument(context, input, CalcUnits::ALL)?; let a = Self::parse_argument(context, input, CalcUnits::ALL)?;
input.expect_comma()?; input.expect_comma()?;
let b = Self::parse_argument(context, input, CalcUnits::ALL)?; 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() { if let Ok(a) = a.to_number() {
let b = b.to_number()?; let b = b.to_number()?;
return Ok(a.atan2(b)); return Ok(a.atan2(b));
@ -608,24 +602,80 @@ impl CalcNode {
let a = a.into_length_or_percentage(AllowedNumericType::All)?; let a = a.into_length_or_percentage(AllowedNumericType::All)?;
let b = b.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(())?; 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(a.atan2(b))
Ok(v) => v, })?;
Err(()) => {
return Err(
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
)
},
};
Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) 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>( fn parse_argument<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
@ -727,6 +777,17 @@ impl CalcNode {
Ok(node) 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>` /// Tries to simplify this expression into a `<length>` or `<percentage>`
/// value. /// value.
fn into_length_or_percentage( fn into_length_or_percentage(
@ -809,6 +870,8 @@ impl CalcNode {
round_enabled() round_enabled()
} else if matches!(function, Mod | Rem) { } else if matches!(function, Mod | Rem) {
mod_rem_enabled() mod_rem_enabled()
} else if matches!(function, Pow | Sqrt | Hypot | Log | Exp) {
static_prefs::pref!("layout.css.exp.enabled")
} else { } else {
true true
}; };