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 smallvec::SmallVec;
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 style_traits::{CssWriter, ToCss};
@ -161,6 +161,8 @@ pub enum SortKey {
pub enum GenericCalcNode<L> {
/// A leaf node.
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
/// arguments.
Sum(crate::OwnedSlice<GenericCalcNode<L>>),
@ -247,10 +249,80 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialOrd + PartialEq + ToCss {
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> {
/// Negates the leaf.
fn negate(&mut self) {
self.map(std::ops::Neg::neg);
/// Negate the node inline. If the node is distributive, it is replaced by the result,
/// otherwise the node is wrapped in a [`Negate`] node.
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 {
@ -296,6 +368,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
fn map_internal<L: CalcNodeLeaf>(node: &mut CalcNode<L>, op: &mut impl FnMut(f32) -> f32) {
match node {
CalcNode::Leaf(l) => l.map(op),
CalcNode::Negate(v) => map_internal(v, op),
CalcNode::Sum(children) => {
for node in &mut **children {
map_internal(node, op);
@ -363,6 +436,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
match *self {
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::MinMax(ref c, op) => CalcNode::MinMax(map_children(c, map), op),
Self::Clamp {
@ -440,6 +514,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
{
Ok(match *self {
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) => {
let mut result = Zero::zero();
for child in &**c {
@ -631,6 +706,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
pub fn mul_by(&mut self, scalar: f32) {
match *self {
Self::Leaf(ref mut l) => l.map(|v| v * scalar),
Self::Negate(ref mut value) => value.mul_by(scalar),
// Multiplication is distributive across this.
Self::Sum(ref mut children) => {
for node in &mut **children {
@ -733,6 +809,9 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
child.visit_depth_first_internal(f);
}
},
Self::Negate(ref mut value) => {
value.visit_depth_first_internal(f);
},
Self::Leaf(..) => {},
}
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
/// `<length-percentage>`). Otherwise we can just evaluate it using
/// `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) {
macro_rules! replace_self_with {
($slot:expr) => {{
@ -1062,6 +1143,24 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
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) => {
l.simplify();
},
@ -1073,7 +1172,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
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
W: Write,
{
@ -1111,11 +1210,34 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
dest.write_str("hypot(")?;
true
},
_ => {
if is_outermost {
Self::Negate(_) => {
// 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(")?;
}
is_outermost
true
},
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(", ")?;
}
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) => {
let mut first = true;
for child in &**children {
if !first {
if child.is_negative_leaf() {
dest.write_str(" - ")?;
let mut c = child.clone();
c.negate();
c.to_css_impl(dest, false)?;
} else {
dest.write_str(" + ")?;
child.to_css_impl(dest, false)?;
match child {
Self::Leaf(l) => {
if l.is_negative() {
dest.write_str(" - ")?;
let mut negated = l.clone();
negated.negate();
negated.to_css(dest)?;
} 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 {
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 max,
} => {
min.to_css_impl(dest, false)?;
min.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
center.to_css_impl(dest, false)?;
center.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
max.to_css_impl(dest, false)?;
max.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
},
Self::Round {
ref value,
ref step,
..
} => {
value.to_css_impl(dest, false)?;
value.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
step.to_css_impl(dest, false)?;
step.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
},
Self::ModRem {
ref dividend,
ref divisor,
..
} => {
dividend.to_css_impl(dest, false)?;
dividend.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
divisor.to_css_impl(dest, false)?;
divisor.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
},
Self::Leaf(ref l) => l.to_css(dest)?,
}
@ -1203,6 +1338,6 @@ impl<L: CalcNodeLeaf> ToCss for CalcNode<L> {
where
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('-') => {
let mut rhs = Self::parse_product(context, input, allowed_units)?;
rhs.mul_by(-1.0);
rhs.negate();
sum.push(rhs);
},
_ => {