summaryrefslogtreecommitdiffstats
path: root/servo/components/style/values/generics/calc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/values/generics/calc.rs')
-rw-r--r--servo/components/style/values/generics/calc.rs1104
1 files changed, 1104 insertions, 0 deletions
diff --git a/servo/components/style/values/generics/calc.rs b/servo/components/style/values/generics/calc.rs
new file mode 100644
index 0000000000..24b0cf23a3
--- /dev/null
+++ b/servo/components/style/values/generics/calc.rs
@@ -0,0 +1,1104 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! [Calc expressions][calc].
+//!
+//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
+
+use num_traits::{Float, Zero};
+use smallvec::SmallVec;
+use std::fmt::{self, Write};
+use std::ops::{Add, Div, Mul, Rem, Sub};
+use std::{cmp, mem};
+use style_traits::{CssWriter, ToCss};
+
+/// Whether we're a `min` or `max` function.
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ ToAnimatedZero,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum MinMaxOp {
+ /// `min()`
+ Min,
+ /// `max()`
+ Max,
+}
+
+/// Whether we're a `mod` or `rem` function.
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ ToAnimatedZero,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum ModRemOp {
+ /// `mod()`
+ Mod,
+ /// `rem()`
+ Rem,
+}
+
+/// The strategy used in `round()`
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ ToAnimatedZero,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum RoundingStrategy {
+ /// `round(nearest, a, b)`
+ /// round a to the nearest multiple of b
+ Nearest,
+ /// `round(up, a, b)`
+ /// round a up to the nearest multiple of b
+ Up,
+ /// `round(down, a, b)`
+ /// round a down to the nearest multiple of b
+ Down,
+ /// `round(to-zero, a, b)`
+ /// round a to the nearest multiple of b that is towards zero
+ ToZero,
+}
+
+/// This determines the order in which we serialize members of a calc() sum.
+///
+/// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children
+#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
+#[allow(missing_docs)]
+pub enum SortKey {
+ Number,
+ Percentage,
+ Cap,
+ Ch,
+ Cqb,
+ Cqh,
+ Cqi,
+ Cqmax,
+ Cqmin,
+ Cqw,
+ Deg,
+ Dvb,
+ Dvh,
+ Dvi,
+ Dvmax,
+ Dvmin,
+ Dvw,
+ Em,
+ Ex,
+ Ic,
+ Lvb,
+ Lvh,
+ Lvi,
+ Lvmax,
+ Lvmin,
+ Lvw,
+ Px,
+ Rem,
+ Sec,
+ Svb,
+ Svh,
+ Svi,
+ Svmax,
+ Svmin,
+ Svw,
+ Vb,
+ Vh,
+ Vi,
+ Vmax,
+ Vmin,
+ Vw,
+ Other,
+}
+
+/// A generic node in a calc expression.
+///
+/// FIXME: This would be much more elegant if we used `Self` in the types below,
+/// but we can't because of https://github.com/serde-rs/serde/issues/1565.
+///
+/// FIXME: The following annotations are to workaround an LLVM inlining bug, see
+/// bug 1631929.
+///
+/// cbindgen:destructor-attributes=MOZ_NEVER_INLINE
+/// cbindgen:copy-constructor-attributes=MOZ_NEVER_INLINE
+/// cbindgen:eq-attributes=MOZ_NEVER_INLINE
+#[repr(u8)]
+#[derive(
+ Clone,
+ Debug,
+ Deserialize,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ ToAnimatedZero,
+ ToResolvedValue,
+ ToShmem,
+)]
+pub enum GenericCalcNode<L> {
+ /// A leaf node.
+ Leaf(L),
+ /// A sum node, representing `a + b + c` where a, b, and c are the
+ /// arguments.
+ Sum(crate::OwnedSlice<GenericCalcNode<L>>),
+ /// A `min` or `max` function.
+ MinMax(crate::OwnedSlice<GenericCalcNode<L>>, MinMaxOp),
+ /// A `clamp()` function.
+ Clamp {
+ /// The minimum value.
+ min: Box<GenericCalcNode<L>>,
+ /// The central value.
+ center: Box<GenericCalcNode<L>>,
+ /// The maximum value.
+ max: Box<GenericCalcNode<L>>,
+ },
+ /// A `round()` function.
+ Round {
+ /// The rounding strategy.
+ strategy: RoundingStrategy,
+ /// The value to round.
+ value: Box<GenericCalcNode<L>>,
+ /// The step value.
+ step: Box<GenericCalcNode<L>>,
+ },
+ /// A `mod()` or `rem()` function.
+ ModRem {
+ /// The dividend calculation.
+ dividend: Box<GenericCalcNode<L>>,
+ /// The divisor calculation.
+ divisor: Box<GenericCalcNode<L>>,
+ /// Is the function mod or rem?
+ op: ModRemOp,
+ },
+}
+
+pub use self::GenericCalcNode as CalcNode;
+
+/// A trait that represents all the stuff a valid leaf of a calc expression.
+pub trait CalcNodeLeaf: Clone + Sized + PartialOrd + PartialEq + ToCss {
+ /// Returns the unitless value of this leaf.
+ fn unitless_value(&self) -> f32;
+
+ /// Whether this value is known-negative.
+ fn is_negative(&self) -> bool {
+ self.unitless_value().is_sign_negative()
+ }
+
+ /// Whether this value is infinite.
+ fn is_infinite(&self) -> bool {
+ self.unitless_value().is_infinite()
+ }
+
+ /// Whether this value is zero.
+ fn is_zero(&self) -> bool {
+ self.unitless_value().is_zero()
+ }
+
+ /// Whether this value is NaN.
+ fn is_nan(&self) -> bool {
+ self.unitless_value().is_nan()
+ }
+
+ /// Tries to merge one sum to another, that is, perform `x` + `y`.
+ fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()>;
+
+ /// Tries a generic arithmetic operation.
+ fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
+ where
+ O: Fn(f32, f32) -> f32;
+
+ /// Multiplies the leaf by a given scalar number.
+ fn mul_by(&mut self, scalar: f32);
+
+ /// Negates the leaf.
+ fn negate(&mut self) {
+ self.mul_by(-1.);
+ }
+
+ /// Canonicalizes the expression if necessary.
+ fn simplify(&mut self);
+
+ /// Returns the sort key for simplification.
+ fn sort_key(&self) -> SortKey;
+}
+
+impl<L: CalcNodeLeaf> CalcNode<L> {
+ /// Negates the node.
+ pub fn negate(&mut self) {
+ self.mul_by(-1.);
+ }
+
+ fn sort_key(&self) -> SortKey {
+ match *self {
+ Self::Leaf(ref l) => l.sort_key(),
+ _ => SortKey::Other,
+ }
+ }
+
+ /// Returns the leaf if we can (if simplification has allowed it).
+ pub fn as_leaf(&self) -> Option<&L> {
+ match *self {
+ Self::Leaf(ref l) => Some(l),
+ _ => None,
+ }
+ }
+
+ /// Tries to merge one sum to another, that is, perform `x` + `y`.
+ fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
+ match (self, other) {
+ (&mut CalcNode::Leaf(ref mut one), &CalcNode::Leaf(ref other)) => {
+ one.try_sum_in_place(other)
+ },
+ _ => Err(()),
+ }
+ }
+
+ /// Tries to apply a generic arithmentic operator
+ fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
+ where
+ O: Fn(f32, f32) -> f32,
+ {
+ match (self, other) {
+ (&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => {
+ Ok(CalcNode::Leaf(one.try_op(other, op)?))
+ },
+ _ => Err(()),
+ }
+ }
+
+ /// Convert this `CalcNode` into a `CalcNode` with a different leaf kind.
+ pub fn map_leaves<O, F>(&self, mut map: F) -> CalcNode<O>
+ where
+ O: CalcNodeLeaf,
+ F: FnMut(&L) -> O,
+ {
+ self.map_leaves_internal(&mut map)
+ }
+
+ fn map_leaves_internal<O, F>(&self, map: &mut F) -> CalcNode<O>
+ where
+ O: CalcNodeLeaf,
+ F: FnMut(&L) -> O,
+ {
+ fn map_children<L, O, F>(
+ children: &[CalcNode<L>],
+ map: &mut F,
+ ) -> crate::OwnedSlice<CalcNode<O>>
+ where
+ L: CalcNodeLeaf,
+ O: CalcNodeLeaf,
+ F: FnMut(&L) -> O,
+ {
+ children
+ .iter()
+ .map(|c| c.map_leaves_internal(map))
+ .collect()
+ }
+
+ match *self {
+ Self::Leaf(ref l) => CalcNode::Leaf(map(l)),
+ Self::Sum(ref c) => CalcNode::Sum(map_children(c, map)),
+ Self::MinMax(ref c, op) => CalcNode::MinMax(map_children(c, map), op),
+ Self::Clamp {
+ ref min,
+ ref center,
+ ref max,
+ } => {
+ let min = Box::new(min.map_leaves_internal(map));
+ let center = Box::new(center.map_leaves_internal(map));
+ let max = Box::new(max.map_leaves_internal(map));
+ CalcNode::Clamp { min, center, max }
+ },
+ Self::Round {
+ strategy,
+ ref value,
+ ref step,
+ } => {
+ let value = Box::new(value.map_leaves_internal(map));
+ let step = Box::new(step.map_leaves_internal(map));
+ CalcNode::Round {
+ strategy,
+ value,
+ step,
+ }
+ },
+ Self::ModRem {
+ ref dividend,
+ ref divisor,
+ op,
+ } => {
+ let dividend = Box::new(dividend.map_leaves_internal(map));
+ let divisor = Box::new(divisor.map_leaves_internal(map));
+ CalcNode::ModRem {
+ dividend,
+ divisor,
+ op,
+ }
+ },
+ }
+ }
+
+ /// Resolves the expression returning a value of `O`, given a function to
+ /// turn a leaf into the relevant value.
+ pub fn resolve<O>(
+ &self,
+ mut leaf_to_output_fn: impl FnMut(&L) -> Result<O, ()>,
+ ) -> Result<O, ()>
+ where
+ O: PartialOrd
+ + PartialEq
+ + Add<Output = O>
+ + Mul<Output = O>
+ + Div<Output = O>
+ + Sub<Output = O>
+ + Zero
+ + Float
+ + Copy,
+ {
+ self.resolve_internal(&mut leaf_to_output_fn)
+ }
+
+ fn resolve_internal<O, F>(&self, leaf_to_output_fn: &mut F) -> Result<O, ()>
+ where
+ O: PartialOrd
+ + PartialEq
+ + Add<Output = O>
+ + Mul<Output = O>
+ + Div<Output = O>
+ + Sub<Output = O>
+ + Zero
+ + Float
+ + Copy,
+ F: FnMut(&L) -> Result<O, ()>,
+ {
+ Ok(match *self {
+ Self::Leaf(ref l) => return leaf_to_output_fn(l),
+ Self::Sum(ref c) => {
+ let mut result = Zero::zero();
+ for child in &**c {
+ result = result + child.resolve_internal(leaf_to_output_fn)?;
+ }
+ result
+ },
+ Self::MinMax(ref nodes, op) => {
+ let mut result = nodes[0].resolve_internal(leaf_to_output_fn)?;
+ for node in nodes.iter().skip(1) {
+ let candidate = node.resolve_internal(leaf_to_output_fn)?;
+ let candidate_wins = match op {
+ MinMaxOp::Min => candidate < result,
+ MinMaxOp::Max => candidate > result,
+ };
+ if candidate_wins {
+ result = candidate;
+ }
+ }
+ result
+ },
+ Self::Clamp {
+ ref min,
+ ref center,
+ ref max,
+ } => {
+ let min = min.resolve_internal(leaf_to_output_fn)?;
+ let center = center.resolve_internal(leaf_to_output_fn)?;
+ let max = max.resolve_internal(leaf_to_output_fn)?;
+
+ let mut result = center;
+ if result > max {
+ result = max;
+ }
+ if result < min {
+ result = min
+ }
+ result
+ },
+ Self::Round {
+ strategy,
+ ref value,
+ ref step,
+ } => {
+ let value = value.resolve_internal(leaf_to_output_fn)?;
+ let step = step.resolve_internal(leaf_to_output_fn)?;
+
+ // TODO(emilio): Seems like at least a few of these
+ // special-cases could be removed if we do the math in a
+ // particular order.
+ if step.is_zero() {
+ return Ok(<O as Float>::nan());
+ }
+
+ if value.is_infinite() && step.is_infinite() {
+ return Ok(<O as Float>::nan());
+ }
+
+ if value.is_infinite() {
+ return Ok(value);
+ }
+
+ if step.is_infinite() {
+ match strategy {
+ RoundingStrategy::Nearest | RoundingStrategy::ToZero => {
+ return if value.is_sign_negative() {
+ Ok(<O as Float>::neg_zero())
+ } else {
+ Ok(<O as Zero>::zero())
+ }
+ },
+ RoundingStrategy::Up => {
+ return if !value.is_sign_negative() && !value.is_zero() {
+ Ok(<O as Float>::infinity())
+ } else if !value.is_sign_negative() && value.is_zero() {
+ Ok(value)
+ } else {
+ Ok(<O as Float>::neg_zero())
+ }
+ },
+ RoundingStrategy::Down => {
+ return if value.is_sign_negative() && !value.is_zero() {
+ Ok(<O as Float>::neg_infinity())
+ } else if value.is_sign_negative() && value.is_zero() {
+ Ok(value)
+ } else {
+ Ok(<O as Zero>::zero())
+ }
+ },
+ }
+ }
+
+ let div = value / step;
+ let lower_bound = div.floor() * step;
+ let upper_bound = div.ceil() * step;
+
+ match strategy {
+ RoundingStrategy::Nearest => {
+ // In case of a tie, use the upper bound
+ if value - lower_bound < upper_bound - value {
+ lower_bound
+ } else {
+ upper_bound
+ }
+ },
+ RoundingStrategy::Up => upper_bound,
+ RoundingStrategy::Down => lower_bound,
+ RoundingStrategy::ToZero => {
+ // In case of a tie, use the upper bound
+ if lower_bound.abs() < upper_bound.abs() {
+ lower_bound
+ } else {
+ upper_bound
+ }
+ },
+ }
+ },
+ Self::ModRem {
+ ref dividend,
+ ref divisor,
+ op,
+ } => {
+ let dividend = dividend.resolve_internal(leaf_to_output_fn)?;
+ let divisor = divisor.resolve_internal(leaf_to_output_fn)?;
+
+ // In mod(A, B) only, if B is infinite and A has opposite sign to B
+ // (including an oppositely-signed zero), the result is NaN.
+ // https://drafts.csswg.org/css-values/#round-infinities
+ if matches!(op, ModRemOp::Mod) &&
+ divisor.is_infinite() &&
+ dividend.is_sign_negative() != divisor.is_sign_negative()
+ {
+ return Ok(<O as Float>::nan());
+ }
+
+ match op {
+ ModRemOp::Mod => dividend - divisor * (dividend / divisor).floor(),
+ ModRemOp::Rem => dividend - divisor * (dividend / divisor).trunc(),
+ }
+ },
+ })
+ }
+
+ fn is_negative_leaf(&self) -> bool {
+ match *self {
+ Self::Leaf(ref l) => l.is_negative(),
+ _ => false,
+ }
+ }
+
+ fn is_zero_leaf(&self) -> bool {
+ match *self {
+ Self::Leaf(ref l) => l.is_zero(),
+ _ => false,
+ }
+ }
+
+ fn is_infinite_leaf(&self) -> bool {
+ match *self {
+ Self::Leaf(ref l) => l.is_infinite(),
+ _ => false,
+ }
+ }
+
+ /// Multiplies the node by a scalar.
+ pub fn mul_by(&mut self, scalar: f32) {
+ match *self {
+ Self::Leaf(ref mut l) => l.mul_by(scalar),
+ // Multiplication is distributive across this.
+ Self::Sum(ref mut children) => {
+ for node in &mut **children {
+ node.mul_by(scalar);
+ }
+ },
+ // This one is a bit trickier.
+ Self::MinMax(ref mut children, ref mut op) => {
+ for node in &mut **children {
+ node.mul_by(scalar);
+ }
+
+ // For negatives we need to invert the operation.
+ if scalar < 0. {
+ *op = match *op {
+ MinMaxOp::Min => MinMaxOp::Max,
+ MinMaxOp::Max => MinMaxOp::Min,
+ }
+ }
+ },
+ // This one is slightly tricky too.
+ Self::Clamp {
+ ref mut min,
+ ref mut center,
+ ref mut max,
+ } => {
+ min.mul_by(scalar);
+ center.mul_by(scalar);
+ max.mul_by(scalar);
+ // For negatives we need to swap min / max.
+ if scalar < 0. {
+ mem::swap(min, max);
+ }
+ },
+ Self::Round {
+ ref mut value,
+ ref mut step,
+ ..
+ } => {
+ value.mul_by(scalar);
+ step.mul_by(scalar);
+ },
+ Self::ModRem {
+ ref mut dividend,
+ ref mut divisor,
+ ..
+ } => {
+ dividend.mul_by(scalar);
+ divisor.mul_by(scalar);
+ },
+ }
+ }
+
+ /// Visits all the nodes in this calculation tree recursively, starting by
+ /// the leaves and bubbling all the way up.
+ ///
+ /// This is useful for simplification, but can also be used for validation
+ /// and such.
+ pub fn visit_depth_first(&mut self, mut f: impl FnMut(&mut Self)) {
+ self.visit_depth_first_internal(&mut f);
+ }
+
+ fn visit_depth_first_internal(&mut self, f: &mut impl FnMut(&mut Self)) {
+ match *self {
+ Self::Clamp {
+ ref mut min,
+ ref mut center,
+ ref mut max,
+ } => {
+ min.visit_depth_first_internal(f);
+ center.visit_depth_first_internal(f);
+ max.visit_depth_first_internal(f);
+ },
+ Self::Round {
+ ref mut value,
+ ref mut step,
+ ..
+ } => {
+ value.visit_depth_first_internal(f);
+ step.visit_depth_first_internal(f);
+ },
+ Self::ModRem {
+ ref mut dividend,
+ ref mut divisor,
+ ..
+ } => {
+ dividend.visit_depth_first_internal(f);
+ divisor.visit_depth_first_internal(f);
+ },
+ Self::Sum(ref mut children) | Self::MinMax(ref mut children, _) => {
+ for child in &mut **children {
+ child.visit_depth_first_internal(f);
+ }
+ },
+ Self::Leaf(..) => {},
+ }
+ f(self);
+ }
+
+ /// Simplifies and sorts the calculation of a given node. All the nodes
+ /// below it should be simplified already, this only takes care of
+ /// simplifying directly nested nodes. So, probably should always be used in
+ /// combination with `visit_depth_first()`.
+ ///
+ /// 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.
+ pub fn simplify_and_sort_direct_children(&mut self) {
+ macro_rules! replace_self_with {
+ ($slot:expr) => {{
+ let dummy = Self::MinMax(Default::default(), MinMaxOp::Max);
+ let result = mem::replace($slot, dummy);
+ *self = result;
+ }};
+ }
+ match *self {
+ Self::Clamp {
+ ref mut min,
+ ref mut center,
+ ref mut max,
+ } => {
+ // NOTE: clamp() is max(min, min(center, max))
+ let min_cmp_center = match min.partial_cmp(&center) {
+ Some(o) => o,
+ None => return,
+ };
+
+ // So if we can prove that min is more than center, then we won,
+ // as that's what we should always return.
+ if matches!(min_cmp_center, cmp::Ordering::Greater) {
+ return replace_self_with!(&mut **min);
+ }
+
+ // Otherwise try with max.
+ let max_cmp_center = match max.partial_cmp(&center) {
+ Some(o) => o,
+ None => return,
+ };
+
+ if matches!(max_cmp_center, cmp::Ordering::Less) {
+ // max is less than center, so we need to return effectively
+ // `max(min, max)`.
+ let max_cmp_min = match max.partial_cmp(&min) {
+ Some(o) => o,
+ None => {
+ debug_assert!(
+ false,
+ "We compared center with min and max, how are \
+ min / max not comparable with each other?"
+ );
+ return;
+ },
+ };
+
+ if matches!(max_cmp_min, cmp::Ordering::Less) {
+ return replace_self_with!(&mut **min);
+ }
+
+ return replace_self_with!(&mut **max);
+ }
+
+ // Otherwise we're the center node.
+ return replace_self_with!(&mut **center);
+ },
+ Self::Round {
+ strategy,
+ ref mut value,
+ ref mut step,
+ } => {
+ if step.is_zero_leaf() {
+ value.mul_by(f32::NAN);
+ return replace_self_with!(&mut **value);
+ }
+
+ if value.is_infinite_leaf() && step.is_infinite_leaf() {
+ value.mul_by(f32::NAN);
+ return replace_self_with!(&mut **value);
+ }
+
+ if value.is_infinite_leaf() {
+ return replace_self_with!(&mut **value);
+ }
+
+ if step.is_infinite_leaf() {
+ match strategy {
+ RoundingStrategy::Nearest | RoundingStrategy::ToZero => {
+ value.mul_by(0.);
+ return replace_self_with!(&mut **value);
+ },
+ RoundingStrategy::Up => {
+ if !value.is_negative_leaf() && !value.is_zero_leaf() {
+ value.mul_by(f32::INFINITY);
+ return replace_self_with!(&mut **value);
+ } else if !value.is_negative_leaf() && value.is_zero_leaf() {
+ return replace_self_with!(&mut **value);
+ } else {
+ value.mul_by(0.);
+ return replace_self_with!(&mut **value);
+ }
+ },
+ RoundingStrategy::Down => {
+ if value.is_negative_leaf() && !value.is_zero_leaf() {
+ value.mul_by(f32::INFINITY);
+ return replace_self_with!(&mut **value);
+ } else if value.is_negative_leaf() && value.is_zero_leaf() {
+ return replace_self_with!(&mut **value);
+ } else {
+ value.mul_by(0.);
+ return replace_self_with!(&mut **value);
+ }
+ },
+ }
+ }
+
+ if step.is_negative_leaf() {
+ step.negate();
+ }
+
+ let remainder = match value.try_op(step, Rem::rem) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ let (mut lower_bound, mut upper_bound) = if value.is_negative_leaf() {
+ let upper_bound = match value.try_op(&remainder, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ let lower_bound = match upper_bound.try_op(&step, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ (lower_bound, upper_bound)
+ } else {
+ let lower_bound = match value.try_op(&remainder, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ let upper_bound = match lower_bound.try_op(&step, Add::add) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ (lower_bound, upper_bound)
+ };
+
+ match strategy {
+ RoundingStrategy::Nearest => {
+ let lower_diff = match value.try_op(&lower_bound, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ let upper_diff = match upper_bound.try_op(value, Sub::sub) {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ // In case of a tie, use the upper bound
+ if lower_diff < upper_diff {
+ return replace_self_with!(&mut lower_bound);
+ } else {
+ return replace_self_with!(&mut upper_bound);
+ }
+ },
+ RoundingStrategy::Up => return replace_self_with!(&mut upper_bound),
+ RoundingStrategy::Down => return replace_self_with!(&mut lower_bound),
+ RoundingStrategy::ToZero => {
+ let mut lower_diff = lower_bound.clone();
+ let mut upper_diff = upper_bound.clone();
+
+ if lower_diff.is_negative_leaf() {
+ lower_diff.negate();
+ }
+
+ if upper_diff.is_negative_leaf() {
+ upper_diff.negate();
+ }
+
+ // In case of a tie, use the upper bound
+ if lower_diff < upper_diff {
+ return replace_self_with!(&mut lower_bound);
+ } else {
+ return replace_self_with!(&mut upper_bound);
+ }
+ },
+ };
+ },
+ Self::ModRem {
+ ref dividend,
+ ref divisor,
+ op,
+ } => {
+ let mut result = dividend.clone();
+
+ // In mod(A, B) only, if B is infinite and A has opposite sign to B
+ // (including an oppositely-signed zero), the result is NaN.
+ // https://drafts.csswg.org/css-values/#round-infinities
+ if matches!(op, ModRemOp::Mod) &&
+ divisor.is_infinite_leaf() &&
+ dividend.is_negative_leaf() != divisor.is_negative_leaf()
+ {
+ result.mul_by(f32::NAN);
+ return replace_self_with!(&mut *result);
+ }
+
+ let result = match op {
+ ModRemOp::Mod => dividend.try_op(divisor, |a, b| a - b * (a / b).floor()),
+ ModRemOp::Rem => dividend.try_op(divisor, |a, b| a - b * (a / b).trunc()),
+ };
+
+ let mut result = match result {
+ Ok(res) => res,
+ Err(..) => return,
+ };
+
+ return replace_self_with!(&mut result);
+ },
+ Self::MinMax(ref mut children, op) => {
+ let winning_order = match op {
+ MinMaxOp::Min => cmp::Ordering::Less,
+ MinMaxOp::Max => cmp::Ordering::Greater,
+ };
+
+ let mut result = 0;
+ for i in 1..children.len() {
+ let o = match children[i].partial_cmp(&children[result]) {
+ // We can't compare all the children, so we can't
+ // know which one will actually win. Bail out and
+ // keep ourselves as a min / max function.
+ //
+ // TODO: Maybe we could simplify compatible children,
+ // see https://github.com/w3c/csswg-drafts/issues/4756
+ None => return,
+ Some(o) => o,
+ };
+
+ if o == winning_order {
+ result = i;
+ }
+ }
+
+ replace_self_with!(&mut children[result]);
+ },
+ Self::Sum(ref mut children_slot) => {
+ let mut sums_to_merge = SmallVec::<[_; 3]>::new();
+ let mut extra_kids = 0;
+ for (i, child) in children_slot.iter().enumerate() {
+ if let Self::Sum(ref children) = *child {
+ extra_kids += children.len();
+ sums_to_merge.push(i);
+ }
+ }
+
+ // If we only have one kid, we've already simplified it, and it
+ // doesn't really matter whether it's a sum already or not, so
+ // lift it up and continue.
+ if children_slot.len() == 1 {
+ return replace_self_with!(&mut children_slot[0]);
+ }
+
+ let mut children = mem::replace(children_slot, Default::default()).into_vec();
+
+ if !sums_to_merge.is_empty() {
+ children.reserve(extra_kids - sums_to_merge.len());
+ // Merge all our nested sums, in reverse order so that the
+ // list indices are not invalidated.
+ for i in sums_to_merge.drain(..).rev() {
+ let kid_children = match children.swap_remove(i) {
+ Self::Sum(c) => c,
+ _ => unreachable!(),
+ };
+
+ // This would be nicer with
+ // https://github.com/rust-lang/rust/issues/59878 fixed.
+ children.extend(kid_children.into_vec());
+ }
+ }
+
+ debug_assert!(children.len() >= 2, "Should still have multiple kids!");
+
+ // Sort by spec order.
+ children.sort_unstable_by_key(|c| c.sort_key());
+
+ // NOTE: if the function returns true, by the docs of dedup_by,
+ // a is removed.
+ children.dedup_by(|a, b| b.try_sum_in_place(a).is_ok());
+
+ if children.len() == 1 {
+ // If only one children remains, lift it up, and carry on.
+ replace_self_with!(&mut children[0]);
+ } else {
+ // Else put our simplified children back.
+ *children_slot = children.into_boxed_slice().into();
+ }
+ },
+ Self::Leaf(ref mut l) => {
+ l.simplify();
+ },
+ }
+ }
+
+ /// Simplifies and sorts the kids in the whole calculation subtree.
+ pub fn simplify_and_sort(&mut self) {
+ 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
+ where
+ W: Write,
+ {
+ let write_closing_paren = match *self {
+ Self::MinMax(_, op) => {
+ dest.write_str(match op {
+ MinMaxOp::Max => "max(",
+ MinMaxOp::Min => "min(",
+ })?;
+ true
+ },
+ Self::Clamp { .. } => {
+ dest.write_str("clamp(")?;
+ true
+ },
+ Self::Round { strategy, .. } => {
+ match strategy {
+ RoundingStrategy::Nearest => dest.write_str("round("),
+ RoundingStrategy::Up => dest.write_str("round(up, "),
+ RoundingStrategy::Down => dest.write_str("round(down, "),
+ RoundingStrategy::ToZero => dest.write_str("round(to-zero, "),
+ }?;
+
+ true
+ },
+ Self::ModRem { op, .. } => {
+ dest.write_str(match op {
+ ModRemOp::Mod => "mod(",
+ ModRemOp::Rem => "rem(",
+ })?;
+
+ true
+ },
+ _ => {
+ if is_outermost {
+ dest.write_str("calc(")?;
+ }
+ is_outermost
+ },
+ };
+
+ match *self {
+ Self::MinMax(ref children, _) => {
+ let mut first = true;
+ for child in &**children {
+ if !first {
+ dest.write_str(", ")?;
+ }
+ first = false;
+ child.to_css_impl(dest, false)?;
+ }
+ },
+ 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)?;
+ }
+ } else {
+ first = false;
+ child.to_css_impl(dest, false)?;
+ }
+ }
+ },
+ Self::Clamp {
+ ref min,
+ ref center,
+ ref max,
+ } => {
+ min.to_css_impl(dest, false)?;
+ dest.write_str(", ")?;
+ center.to_css_impl(dest, false)?;
+ dest.write_str(", ")?;
+ max.to_css_impl(dest, false)?;
+ },
+ Self::Round {
+ ref value,
+ ref step,
+ ..
+ } => {
+ value.to_css_impl(dest, false)?;
+ dest.write_str(", ")?;
+ step.to_css_impl(dest, false)?;
+ },
+ Self::ModRem {
+ ref dividend,
+ ref divisor,
+ ..
+ } => {
+ dividend.to_css_impl(dest, false)?;
+ dest.write_str(", ")?;
+ divisor.to_css_impl(dest, false)?;
+ },
+ Self::Leaf(ref l) => l.to_css(dest)?,
+ }
+
+ if write_closing_paren {
+ dest.write_str(")")?;
+ }
+ Ok(())
+ }
+}
+
+impl<L: CalcNodeLeaf> PartialOrd for CalcNode<L> {
+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+ match (self, other) {
+ (&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => one.partial_cmp(other),
+ _ => None,
+ }
+ }
+}
+
+impl<L: CalcNodeLeaf> ToCss for CalcNode<L> {
+ /// <https://drafts.csswg.org/css-values/#calc-serialize>
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.to_css_impl(dest, /* is_outermost = */ true)
+ }
+}