mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
style: Implement CSS exponential functions
Differential Revision: https://phabricator.services.mozilla.com/D170842
This commit is contained in:
parent
0b20b343e6
commit
21d1bdeb9b
2 changed files with 147 additions and 40 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue