style: Add negate node to use in place of mul_by in sum nodes

Sum nodes would use mul_by to negate nodes to do subtraction, but some
nodes are not distributive.  This patch adds a negate node, so that the
operations inside these negate nodes can be resolved first and then the
"subtraction" can be applied.

Differential Revision: https://phabricator.services.mozilla.com/D172941
This commit is contained in:
Tiaan Louw 2023-05-03 14:04:28 +00:00 committed by Martin Robinson
parent 9fd6f09a41
commit a6b97563c3
2 changed files with 163 additions and 28 deletions

View file

@ -9,7 +9,7 @@
use num_traits::{Float, Zero}; use num_traits::{Float, Zero};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::ops::{Add, Div, Mul, Rem, Sub}; use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
use std::{cmp, mem}; use std::{cmp, mem};
use style_traits::{CssWriter, ToCss}; use style_traits::{CssWriter, ToCss};
@ -161,6 +161,8 @@ pub enum SortKey {
pub enum GenericCalcNode<L> { pub enum GenericCalcNode<L> {
/// A leaf node. /// A leaf node.
Leaf(L), Leaf(L),
/// A node that negates its children, e.g. Negate(1) == -1.
Negate(Box<GenericCalcNode<L>>),
/// A sum node, representing `a + b + c` where a, b, and c are the /// A sum node, representing `a + b + c` where a, b, and c are the
/// arguments. /// arguments.
Sum(crate::OwnedSlice<GenericCalcNode<L>>), Sum(crate::OwnedSlice<GenericCalcNode<L>>),
@ -247,10 +249,80 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialOrd + PartialEq + ToCss {
fn sort_key(&self) -> SortKey; fn sort_key(&self) -> SortKey;
} }
/// The level of any argument being serialized in `to_css_impl`.
enum ArgumentLevel {
/// The root of a calculation tree.
CalculationRoot,
/// The root of an operand node's argument, e.g. `min(10, 20)`, `10` and `20` will have this
/// level, but min in this case will have `TopMost`.
ArgumentRoot,
/// Any other values serialized in the tree.
Nested,
}
impl<L: CalcNodeLeaf> CalcNode<L> { impl<L: CalcNodeLeaf> CalcNode<L> {
/// Negates the leaf. /// Negate the node inline. If the node is distributive, it is replaced by the result,
fn negate(&mut self) { /// otherwise the node is wrapped in a [`Negate`] node.
self.map(std::ops::Neg::neg); pub fn negate(&mut self) {
match *self {
CalcNode::Leaf(ref mut leaf) => leaf.map(|l| l.neg()),
CalcNode::Negate(ref mut value) => {
// Don't negate the value here. Replace `self` with it's child.
let result = mem::replace(
value.as_mut(),
Self::MinMax(Default::default(), MinMaxOp::Max),
);
*self = result;
},
CalcNode::Sum(ref mut children) => {
for child in children.iter_mut() {
child.negate();
}
},
CalcNode::MinMax(ref mut children, ref mut op) => {
for child in children.iter_mut() {
child.negate();
}
// Negating min-max means the operation is swapped.
*op = match *op {
MinMaxOp::Min => MinMaxOp::Max,
MinMaxOp::Max => MinMaxOp::Min,
};
},
CalcNode::Clamp {
ref mut min,
ref mut center,
ref mut max,
} => {
min.negate();
center.negate();
max.negate();
mem::swap(min, max);
},
CalcNode::Round {
ref mut value,
ref mut step,
..
} => {
value.negate();
step.negate();
},
CalcNode::ModRem {
ref mut dividend,
ref mut divisor,
..
} => {
dividend.negate();
divisor.negate();
},
CalcNode::Hypot(ref mut children) => {
for child in children.iter_mut() {
child.negate();
}
},
}
} }
fn sort_key(&self) -> SortKey { fn sort_key(&self) -> SortKey {
@ -296,6 +368,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
fn map_internal<L: CalcNodeLeaf>(node: &mut CalcNode<L>, op: &mut impl FnMut(f32) -> f32) { fn map_internal<L: CalcNodeLeaf>(node: &mut CalcNode<L>, op: &mut impl FnMut(f32) -> f32) {
match node { match node {
CalcNode::Leaf(l) => l.map(op), CalcNode::Leaf(l) => l.map(op),
CalcNode::Negate(v) => map_internal(v, op),
CalcNode::Sum(children) => { CalcNode::Sum(children) => {
for node in &mut **children { for node in &mut **children {
map_internal(node, op); map_internal(node, op);
@ -363,6 +436,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
match *self { match *self {
Self::Leaf(ref l) => CalcNode::Leaf(map(l)), Self::Leaf(ref l) => CalcNode::Leaf(map(l)),
Self::Negate(ref c) => CalcNode::Negate(Box::new(c.map_leaves_internal(map))),
Self::Sum(ref c) => CalcNode::Sum(map_children(c, map)), Self::Sum(ref c) => CalcNode::Sum(map_children(c, map)),
Self::MinMax(ref c, op) => CalcNode::MinMax(map_children(c, map), op), Self::MinMax(ref c, op) => CalcNode::MinMax(map_children(c, map), op),
Self::Clamp { Self::Clamp {
@ -440,6 +514,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
{ {
Ok(match *self { Ok(match *self {
Self::Leaf(ref l) => return leaf_to_output_fn(l), Self::Leaf(ref l) => return leaf_to_output_fn(l),
Self::Negate(ref c) => c.resolve_internal(leaf_to_output_fn)?.neg(),
Self::Sum(ref c) => { Self::Sum(ref c) => {
let mut result = Zero::zero(); let mut result = Zero::zero();
for child in &**c { for child in &**c {
@ -631,6 +706,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
pub fn mul_by(&mut self, scalar: f32) { pub fn mul_by(&mut self, scalar: f32) {
match *self { match *self {
Self::Leaf(ref mut l) => l.map(|v| v * scalar), Self::Leaf(ref mut l) => l.map(|v| v * scalar),
Self::Negate(ref mut value) => value.mul_by(scalar),
// Multiplication is distributive across this. // Multiplication is distributive across this.
Self::Sum(ref mut children) => { Self::Sum(ref mut children) => {
for node in &mut **children { for node in &mut **children {
@ -733,6 +809,9 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
child.visit_depth_first_internal(f); child.visit_depth_first_internal(f);
} }
}, },
Self::Negate(ref mut value) => {
value.visit_depth_first_internal(f);
},
Self::Leaf(..) => {}, Self::Leaf(..) => {},
} }
f(self); f(self);
@ -746,6 +825,8 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
/// This is only needed if it's going to be preserved after parsing (so, for /// This is only needed if it's going to be preserved after parsing (so, for
/// `<length-percentage>`). Otherwise we can just evaluate it using /// `<length-percentage>`). Otherwise we can just evaluate it using
/// `resolve()`, and we'll come up with a simplified value anyways. /// `resolve()`, and we'll come up with a simplified value anyways.
///
/// <https://drafts.csswg.org/css-values-4/#calc-simplification>
pub fn simplify_and_sort_direct_children(&mut self) { pub fn simplify_and_sort_direct_children(&mut self) {
macro_rules! replace_self_with { macro_rules! replace_self_with {
($slot:expr) => {{ ($slot:expr) => {{
@ -1062,6 +1143,24 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
replace_self_with!(&mut result); replace_self_with!(&mut result);
}, },
Self::Negate(ref mut child) => {
// Step 6.
match &mut **child {
CalcNode::Leaf(_) => {
// 1. If roots child is a numeric value, return an equivalent numeric value, but
// with the value negated (0 - value).
child.negate();
replace_self_with!(&mut **child);
},
CalcNode::Negate(value) => {
// 2. If roots child is a Negate node, return the childs child.
replace_self_with!(&mut **value);
},
_ => {
// 3. Return root.
},
}
},
Self::Leaf(ref mut l) => { Self::Leaf(ref mut l) => {
l.simplify(); l.simplify();
}, },
@ -1073,7 +1172,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
self.visit_depth_first(|node| node.simplify_and_sort_direct_children()) self.visit_depth_first(|node| node.simplify_and_sort_direct_children())
} }
fn to_css_impl<W>(&self, dest: &mut CssWriter<W>, is_outermost: bool) -> fmt::Result fn to_css_impl<W>(&self, dest: &mut CssWriter<W>, level: ArgumentLevel) -> fmt::Result
where where
W: Write, W: Write,
{ {
@ -1111,11 +1210,34 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
dest.write_str("hypot(")?; dest.write_str("hypot(")?;
true true
}, },
_ => { Self::Negate(_) => {
if is_outermost { // We never generate a [`Negate`] node as the root of a calculation, only inside
// [`Sum`] nodes as a child. Because negate nodes are handled by the [`Sum`] node
// directly (see below), this node will never be serialized.
debug_assert!(
false,
"We never serialize Negate nodes as they are handled inside Sum nodes."
);
dest.write_str("(-1 * ")?;
true
},
Self::Sum(_) => match level {
ArgumentLevel::CalculationRoot => {
dest.write_str("calc(")?; dest.write_str("calc(")?;
} true
is_outermost },
ArgumentLevel::ArgumentRoot => false,
ArgumentLevel::Nested => {
dest.write_str("(")?;
true
},
},
Self::Leaf(_) => match level {
ArgumentLevel::CalculationRoot => {
dest.write_str("calc(")?;
true
},
ArgumentLevel::ArgumentRoot | ArgumentLevel::Nested => false,
}, },
}; };
@ -1127,25 +1249,38 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
dest.write_str(", ")?; dest.write_str(", ")?;
} }
first = false; first = false;
child.to_css_impl(dest, false)?; child.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
} }
}, },
Self::Negate(ref value) => value.to_css_impl(dest, ArgumentLevel::Nested)?,
Self::Sum(ref children) => { Self::Sum(ref children) => {
let mut first = true; let mut first = true;
for child in &**children { for child in &**children {
if !first { if !first {
if child.is_negative_leaf() { match child {
dest.write_str(" - ")?; Self::Leaf(l) => {
let mut c = child.clone(); if l.is_negative() {
c.negate(); dest.write_str(" - ")?;
c.to_css_impl(dest, false)?; let mut negated = l.clone();
} else { negated.negate();
dest.write_str(" + ")?; negated.to_css(dest)?;
child.to_css_impl(dest, false)?; } else {
dest.write_str(" + ")?;
l.to_css(dest)?;
}
},
Self::Negate(n) => {
dest.write_str(" - ")?;
n.to_css_impl(dest, ArgumentLevel::Nested)?;
},
_ => {
dest.write_str(" + ")?;
child.to_css_impl(dest, ArgumentLevel::Nested)?;
},
} }
} else { } else {
first = false; first = false;
child.to_css_impl(dest, false)?; child.to_css_impl(dest, ArgumentLevel::Nested)?;
} }
} }
}, },
@ -1154,29 +1289,29 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
ref center, ref center,
ref max, ref max,
} => { } => {
min.to_css_impl(dest, false)?; min.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?; dest.write_str(", ")?;
center.to_css_impl(dest, false)?; center.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?; dest.write_str(", ")?;
max.to_css_impl(dest, false)?; max.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
}, },
Self::Round { Self::Round {
ref value, ref value,
ref step, ref step,
.. ..
} => { } => {
value.to_css_impl(dest, false)?; value.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?; dest.write_str(", ")?;
step.to_css_impl(dest, false)?; step.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
}, },
Self::ModRem { Self::ModRem {
ref dividend, ref dividend,
ref divisor, ref divisor,
.. ..
} => { } => {
dividend.to_css_impl(dest, false)?; dividend.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?; dest.write_str(", ")?;
divisor.to_css_impl(dest, false)?; divisor.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
}, },
Self::Leaf(ref l) => l.to_css(dest)?, Self::Leaf(ref l) => l.to_css(dest)?,
} }
@ -1203,6 +1338,6 @@ impl<L: CalcNodeLeaf> ToCss for CalcNode<L> {
where where
W: Write, W: Write,
{ {
self.to_css_impl(dest, /* is_outermost = */ true) self.to_css_impl(dest, ArgumentLevel::CalculationRoot)
} }
} }

View file

@ -724,7 +724,7 @@ impl CalcNode {
}, },
Token::Delim('-') => { Token::Delim('-') => {
let mut rhs = Self::parse_product(context, input, allowed_units)?; let mut rhs = Self::parse_product(context, input, allowed_units)?;
rhs.mul_by(-1.0); rhs.negate();
sum.push(rhs); sum.push(rhs);
}, },
_ => { _ => {