Source code

Revision control

Copy as Markdown

Other Tools

/* 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].
//!
use crate::derives::*;
use crate::typed_om::{MathValue, NumericValue, ToTyped, TypedValue};
use crate::values::generics::length::GenericAnchorSizeFunction;
use crate::values::generics::position::{GenericAnchorFunction, GenericAnchorSide};
use crate::values::generics::Optional;
use num_traits::Zero;
use smallvec::SmallVec;
use std::convert::AsRef;
use std::fmt::{self, Write};
use std::ops::{Add, Mul, Neg, Rem, Sub};
use std::{cmp, mem};
use strum_macros::AsRefStr;
use style_traits::{CssWriter, ToCss};
use thin_vec::ThinVec;
/// 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,
}
impl ModRemOp {
fn apply(self, dividend: f32, divisor: f32) -> f32 {
// 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.
if matches!(self, Self::Mod)
&& divisor.is_infinite()
&& dividend.is_sign_negative() != divisor.is_sign_negative()
{
return f32::NAN;
}
let (r, same_sign_as) = match self {
Self::Mod => (dividend - divisor * (dividend / divisor).floor(), divisor),
Self::Rem => (dividend - divisor * (dividend / divisor).trunc(), dividend),
};
if r == 0.0 && same_sign_as.is_sign_negative() {
-0.0
} else {
r
}
}
}
/// 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.
///
#[derive(
AsRefStr, Clone, Copy, Debug, Eq, Ord, Parse, PartialEq, PartialOrd, MallocSizeOf, ToShmem,
)]
#[strum(serialize_all = "lowercase")]
#[allow(missing_docs)]
pub enum SortKey {
#[strum(serialize = "")]
Number,
#[css(skip)]
#[strum(serialize = "%")]
Percentage,
Cap,
Ch,
Cqb,
Cqh,
Cqi,
Cqmax,
Cqmin,
Cqw,
Deg,
Dppx,
Dvb,
Dvh,
Dvi,
Dvmax,
Dvmin,
Dvw,
Em,
Ex,
Ic,
Lh,
Lvb,
Lvh,
Lvi,
Lvmax,
Lvmin,
Lvw,
Ms,
Px,
Rcap,
Rch,
Rem,
Rex,
Ric,
Rlh,
S, // Sec
Svb,
Svh,
Svi,
Svmax,
Svmin,
Svw,
Vb,
Vh,
Vi,
Vmax,
Vmin,
Vw,
#[css(skip)]
ColorComponent,
#[css(skip)]
Other,
}
/// Fallback type for anchor functions within `calc()`.
/// Ideally, the fallback type is initial type of the property (e.g.
/// `GenericInset` for `left`), but that causes circular reference.
/// TODO(dshin, bug 2034100): Investigate ways to not require this.
/// This handles the parsing of unitless zeros, as well as ensuring
/// that e.g. `calc(anchor(--foo left, 1px) + 10%)` round trips
/// (sorting aside), instead of becoming
/// `calc(anchor(--foo left, calc(1px)) + 10%)`.
#[repr(C)]
#[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
ToAnimatedZero,
ToResolvedValue,
ToShmem,
)]
pub struct GenericAnchorFunctionFallback<L> {
/// Was this node parsed as a calc node?
#[animation(constant)]
is_calc_node: bool,
/// The parsed fallback value. Stored as a calc node to break
/// the circular reference.
pub node: GenericCalcNode<L>,
}
impl<L> GenericAnchorFunctionFallback<L> {
/// Create a new anchor function fallback value.
pub fn new(is_calc_node: bool, node: GenericCalcNode<L>) -> Self {
Self { is_calc_node, node }
}
}
impl<L: CalcNodeLeaf> ToCss for GenericAnchorFunctionFallback<L> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
self.node.to_css_impl(
dest,
if self.is_calc_node {
ArgumentLevel::CalculationRoot
} else {
ArgumentLevel::ArgumentRoot
},
)
}
}
/// `anchor()` function used in math functions.
pub type GenericCalcAnchorFunction<L> =
GenericAnchorFunction<Box<GenericCalcNode<L>>, Box<GenericAnchorFunctionFallback<L>>>;
/// `anchor-size()` function used in math functions.
pub type GenericCalcAnchorSizeFunction<L> =
GenericAnchorSizeFunction<Box<GenericAnchorFunctionFallback<L>>>;
/// 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
///
/// 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 node that negates its child, e.g. Negate(1) == -1.
Negate(Box<Self>),
/// A node that inverts its child, e.g. Invert(10) == 1 / 10 == 0.1. The child must always
/// resolve to a number unit.
Invert(Box<Self>),
/// A sum node, representing `a + b + c` where a, b, and c are the
/// arguments.
Sum(crate::OwnedSlice<Self>),
/// A product node, representing `a * b * c` where a, b, and c are the
/// arguments.
Product(crate::OwnedSlice<Self>),
/// A `min` or `max` function.
MinMax(crate::OwnedSlice<Self>, MinMaxOp),
/// A `clamp()` function.
Clamp {
/// The minimum value.
min: Box<Self>,
/// The central value.
center: Box<Self>,
/// The maximum value.
max: Box<Self>,
},
/// A `round()` function.
Round {
/// The rounding strategy.
strategy: RoundingStrategy,
/// The value to round.
value: Box<Self>,
/// The step value.
step: Box<Self>,
},
/// A `mod()` or `rem()` function.
ModRem {
/// The dividend calculation.
dividend: Box<Self>,
/// The divisor calculation.
divisor: Box<Self>,
/// Is the function mod or rem?
op: ModRemOp,
},
/// A `sin()` function.
Sin(Box<Self>),
/// A `cos()` function.
Cos(Box<Self>),
/// A `tan()` function.
Tan(Box<Self>),
/// An `asin()` function.
Asin(Box<Self>),
/// An `acos()` function.
Acos(Box<Self>),
/// An `atan()` function.
Atan(Box<Self>),
/// An `atan2()` function.
Atan2(Box<Self>, Box<Self>),
/// A `pow()` function.
Pow(Box<Self>, Box<Self>),
/// A `sqrt()` function.
Sqrt(Box<Self>),
/// A `hypot()` function
Hypot(crate::OwnedSlice<Self>),
/// A `log()` function.
Log(Box<Self>, Optional<Box<Self>>),
/// An `exp()` function.
Exp(Box<Self>),
/// An `abs()` function.
Abs(Box<Self>),
/// A `sign()` function.
Sign(Box<Self>),
/// An `anchor()` function.
Anchor(Box<GenericCalcAnchorFunction<L>>),
/// An `anchor-size()` function.
AnchorSize(Box<GenericCalcAnchorSizeFunction<L>>),
}
pub use self::GenericCalcNode as CalcNode;
bitflags! {
/// Expected units we allow parsing within a `calc()` expression.
///
/// This is used as a hint for the parser to fast-reject invalid
/// expressions. Numbers are always allowed because they multiply other
/// units.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct CalcUnits: u8 {
/// <length>
const LENGTH = 1 << 0;
/// <percentage>
const PERCENTAGE = 1 << 1;
/// <angle>
const ANGLE = 1 << 2;
/// <time>
const TIME = 1 << 3;
/// <resolution>
const RESOLUTION = 1 << 4;
/// <length-percentage>
const LENGTH_PERCENTAGE = Self::LENGTH.bits() | Self::PERCENTAGE.bits();
// NOTE: When you add to this, make sure to make Atan2 deal with these.
/// Allow all units.
const ALL = Self::LENGTH.bits() | Self::PERCENTAGE.bits() | Self::ANGLE.bits() |
Self::TIME.bits() | Self::RESOLUTION.bits();
}
}
impl CalcUnits {
/// Returns whether the flags only represent a single unit. This will return true for 0, which
/// is a "number" this is also fine.
#[inline]
fn is_single_unit(&self) -> bool {
self.bits() == 0 || self.bits() & (self.bits() - 1) == 0
}
/// Returns true if this unit is allowed to be summed with the given unit, otherwise false.
#[inline]
fn can_sum_with(&self, other: Self) -> bool {
match *self {
Self::LENGTH => other.intersects(Self::LENGTH | Self::PERCENTAGE),
Self::PERCENTAGE => other.intersects(Self::LENGTH | Self::PERCENTAGE),
Self::LENGTH_PERCENTAGE => other.intersects(Self::LENGTH | Self::PERCENTAGE),
u => u.is_single_unit() && other == u,
}
}
}
/// For percentage resolution, sometimes we can't assume that the percentage basis is positive (so
/// we don't know whether a percentage is larger than another).
pub enum PositivePercentageBasis {
/// The percent basis is not known-positive, we can't compare percentages.
Unknown,
/// The percent basis is known-positive, we assume larger percentages are larger.
Yes,
}
macro_rules! compare_helpers {
() => {
/// Return whether a leaf is greater than another.
#[allow(unused)]
fn gt(&self, other: &Self, basis_positive: PositivePercentageBasis) -> bool {
self.compare(other, basis_positive) == Some(cmp::Ordering::Greater)
}
/// Return whether a leaf is less than another.
fn lt(&self, other: &Self, basis_positive: PositivePercentageBasis) -> bool {
self.compare(other, basis_positive) == Some(cmp::Ordering::Less)
}
/// Return whether a leaf is smaller or equal than another.
fn lte(&self, other: &Self, basis_positive: PositivePercentageBasis) -> bool {
match self.compare(other, basis_positive) {
Some(cmp::Ordering::Less) => true,
Some(cmp::Ordering::Equal) => true,
Some(cmp::Ordering::Greater) => false,
None => false,
}
}
};
}
/// A trait that represents all the stuff a valid leaf of a calc expression.
pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss + ToTyped {
/// Returns the unit of the leaf.
fn unit(&self) -> CalcUnits;
/// Returns the unitless value of this leaf if one is available.
fn unitless_value(&self) -> Option<f32>;
/// Returns the angle value in radians if this leaf is an angle.
fn as_angle_radians(&self) -> Option<f32>;
/// Creates a new angle leaf from a value in radians.
fn new_angle_from_radians(radians: f32) -> Self;
/// Return true if the units of both leaves are equal. (NOTE: Does not take
/// the values into account)
fn is_same_unit_as(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
/// Do a partial comparison of these values.
fn compare(
&self,
other: &Self,
base_is_positive: PositivePercentageBasis,
) -> Option<cmp::Ordering>;
compare_helpers!();
/// Create a new leaf with a number value.
fn new_number(value: f32) -> Self;
/// Returns a float value if the leaf is a number.
fn as_number(&self) -> Option<f32>;
/// Returns a number or angle radians if the leaf is a number or angle.
fn as_number_or_angle_radians(&self) -> Option<f32> {
self.as_number().or_else(|| self.as_angle_radians())
}
/// Whether this value is known-negative.
fn is_negative(&self) -> Result<bool, ()> {
self.unitless_value()
.map(|v| Ok(v.is_sign_negative()))
.unwrap_or_else(|| Err(()))
}
/// Whether this value is infinite.
fn is_infinite(&self) -> Result<bool, ()> {
self.unitless_value()
.map(|v| Ok(v.is_infinite()))
.unwrap_or_else(|| Err(()))
}
/// Whether this value is zero.
fn is_zero(&self) -> Result<bool, ()> {
self.unitless_value()
.map(|v| Ok(v.is_zero()))
.unwrap_or_else(|| Err(()))
}
/// Whether this value is NaN.
fn is_nan(&self) -> Result<bool, ()> {
self.unitless_value()
.map(|v| Ok(v.is_nan()))
.unwrap_or_else(|| Err(()))
}
/// Tries to merge one leaf into another using the sum, that is, perform `x` + `y`.
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()>;
/// Try to merge the right leaf into the left by using a multiplication. Return true if the
/// merge was successful, otherwise false.
fn try_product_in_place(&mut self, other: &mut Self) -> bool;
/// Tries a generic arithmetic operation.
fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
where
O: Fn(f32, f32) -> f32;
/// Map the value of this node with the given operation.
fn map(&mut self, op: impl FnMut(f32) -> f32) -> Result<(), ()>;
/// Canonicalizes the expression if necessary.
fn simplify(&mut self) -> SimplificationResult;
/// Returns the sort key for simplification.
fn sort_key(&self) -> SortKey;
/// Create a new leaf containing the sign() result of the given leaf.
fn sign_from(leaf: &impl CalcNodeLeaf) -> Result<Self, ()> {
let Some(value) = leaf.unitless_value() else {
return Err(());
};
Ok(Self::new_number(if value.is_nan() {
f32::NAN
} else if value.is_zero() {
value
} else if value.is_sign_negative() {
-1.0
} else {
1.0
}))
}
/// Whether this leaf node should serialize with a `calc()` wrapper
/// if this node is the root of the calculation tree.
fn should_serialize_with_root_calc_wrapper(&self) -> bool {
true
}
}
/// The level of any argument being serialized in `to_css_impl`.
#[derive(Clone)]
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,
}
/// The result of simplify_and_sort_direct_children
#[derive(Clone, Copy)]
pub enum SimplificationResult {
/// This node (Or some of its descendants, if any) was simplified.
Simplified,
/// The children was unchanged.
Unchanged,
}
impl<L: CalcNodeLeaf> CalcNode<L> {
/// Create a dummy CalcNode that can be used to do replacements of other nodes.
fn dummy() -> Self {
Self::MinMax(Default::default(), MinMaxOp::Max)
}
/// Change all the leaf nodes to have the given value. This is useful when
/// you have `calc(1px * nan)` and you want to replace the product node with
/// `calc(nan)`, in which case the unit will be retained.
fn coerce_to_value(&mut self, value: f32) -> Result<(), ()> {
self.map(|_| value)
}
/// Return true if a product is distributive over this node.
/// Is distributive: (2 + 3) * 4 = 8 + 12
/// Not distributive: sign(2 + 3) * 4 != sign(8 + 12)
#[inline]
pub fn is_product_distributive(&self) -> bool {
match self {
// If there's no value, we can't distribute the product.
Self::Leaf(l) => l.unitless_value().is_some(),
Self::Sum(children) => children.iter().all(|c| c.is_product_distributive()),
_ => false,
}
}
/// If the node has a valid unit outcome, then return it, otherwise fail.
pub fn unit(&self) -> Result<CalcUnits, ()> {
Ok(match self {
CalcNode::Leaf(l) => l.unit(),
CalcNode::Negate(child) | CalcNode::Abs(child) => child.unit()?,
CalcNode::Sum(children) => {
let mut unit = children.first().unwrap().unit()?;
for child in children.iter().skip(1) {
let child_unit = child.unit()?;
if !child_unit.can_sum_with(unit) {
return Err(());
}
unit |= child_unit;
}
unit
},
CalcNode::Product(children) => {
// Only one node is allowed to have a unit, the rest must be numbers.
let mut unit = None;
for child in children.iter() {
let child_unit = child.unit()?;
if child_unit.is_empty() {
// Numbers are always allowed in a product, so continue with the next.
continue;
}
if unit.is_some() {
// We already have a unit for the node, so another unit node is invalid.
return Err(());
}
// We have the unit for the node.
unit = Some(child_unit);
}
// We only keep track of specified units, so if we end up with a None and no failure
// so far, then we have a number.
unit.unwrap_or(CalcUnits::empty())
},
CalcNode::MinMax(children, _) | CalcNode::Hypot(children) => {
let mut unit = children.first().unwrap().unit()?;
for child in children.iter().skip(1) {
let child_unit = child.unit()?;
if !child_unit.can_sum_with(unit) {
return Err(());
}
unit |= child_unit;
}
unit
},
CalcNode::Clamp { min, center, max } => {
let min_unit = min.unit()?;
let center_unit = center.unit()?;
if !min_unit.can_sum_with(center_unit) {
return Err(());
}
let max_unit = max.unit()?;
if !center_unit.can_sum_with(max_unit) {
return Err(());
}
min_unit | center_unit | max_unit
},
CalcNode::Round { value, step, .. } => {
let value_unit = value.unit()?;
let step_unit = step.unit()?;
if !step_unit.can_sum_with(value_unit) {
return Err(());
}
value_unit | step_unit
},
CalcNode::ModRem {
dividend, divisor, ..
} => {
let dividend_unit = dividend.unit()?;
let divisor_unit = divisor.unit()?;
if !divisor_unit.can_sum_with(dividend_unit) {
return Err(());
}
dividend_unit | divisor_unit
},
CalcNode::Sign(ref child) => {
// sign() always resolves to a number, but we still need to make sure that the
// child units make sense.
let _ = child.unit()?;
CalcUnits::empty()
},
CalcNode::Anchor(..) | CalcNode::AnchorSize(..) => CalcUnits::LENGTH_PERCENTAGE,
CalcNode::Sin(ref child) | CalcNode::Cos(ref child) | CalcNode::Tan(ref child) => {
let child_unit = child.unit()?;
if !child_unit.is_empty() && !child_unit.intersects(CalcUnits::ANGLE) {
return Err(());
}
CalcUnits::empty()
},
CalcNode::Asin(ref child) | CalcNode::Acos(ref child) | CalcNode::Atan(ref child) => {
let child_unit = child.unit()?;
if !child_unit.is_empty() {
return Err(());
}
CalcUnits::ANGLE
},
CalcNode::Atan2(ref a, ref b) => {
let a_unit = a.unit()?;
let b_unit = b.unit()?;
if !a_unit.can_sum_with(b_unit) {
return Err(());
}
CalcUnits::ANGLE
},
CalcNode::Pow(ref a, ref b) => {
let a_unit = a.unit()?;
let b_unit = b.unit()?;
if !a_unit.is_empty() || !b_unit.is_empty() {
return Err(());
}
CalcUnits::empty()
},
CalcNode::Invert(ref c) | CalcNode::Sqrt(ref c) | CalcNode::Exp(ref c) => {
let child_unit = c.unit()?;
if !child_unit.is_empty() {
return Err(());
}
CalcUnits::empty()
},
CalcNode::Log(ref a, ref b) => {
let a_unit = a.unit()?;
let b_unit = match b {
Optional::Some(b) => b.unit()?,
Optional::None => CalcUnits::empty(),
};
if !a_unit.is_empty() || !b_unit.is_empty() {
return Err(());
}
CalcUnits::empty()
},
})
}
/// 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) {
/// Node(params) -> Negate(Node(params))
fn wrap_self_in_negate<L: CalcNodeLeaf>(s: &mut CalcNode<L>) {
let result = mem::replace(s, CalcNode::dummy());
*s = CalcNode::Negate(Box::new(result));
}
match *self {
CalcNode::Leaf(ref mut leaf) => {
if leaf.map(std::ops::Neg::neg).is_err() {
wrap_self_in_negate(self)
}
},
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::dummy());
*self = result;
},
CalcNode::Invert(_) => {
// -(1 / -10) == -(-0.1) == 0.1
wrap_self_in_negate(self)
},
CalcNode::Sum(ref mut children) => {
for child in children.iter_mut() {
child.negate();
}
},
CalcNode::Product(_) => {
// -(2 * 3 / 4) == -(1.5)
wrap_self_in_negate(self);
},
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,
} => {
if min.lte(max, PositivePercentageBasis::Unknown) {
min.negate();
center.negate();
max.negate();
mem::swap(min, max);
} else {
wrap_self_in_negate(self);
}
},
CalcNode::Round {
ref mut strategy,
ref mut value,
ref mut step,
} => {
match *strategy {
RoundingStrategy::Nearest => {
// Nearest is tricky because we'd have to swap the
// behavior at the half-way point from using the upper
// to lower bound.
// Simpler to just wrap self in a negate node.
wrap_self_in_negate(self);
return;
},
RoundingStrategy::Up => *strategy = RoundingStrategy::Down,
RoundingStrategy::Down => *strategy = RoundingStrategy::Up,
RoundingStrategy::ToZero => (),
}
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();
}
},
CalcNode::Sign(ref mut child) => {
child.negate();
},
CalcNode::Sin(..)
| CalcNode::Cos(..)
| CalcNode::Tan(..)
| CalcNode::Asin(..)
| CalcNode::Acos(..)
| CalcNode::Atan(..)
| CalcNode::Atan2(..)
| CalcNode::Pow(..)
| CalcNode::Sqrt(..)
| CalcNode::Log(..)
| CalcNode::Exp(..)
| CalcNode::Abs(..)
| CalcNode::Anchor(..)
| CalcNode::AnchorSize(..) => {
wrap_self_in_negate(self);
},
}
}
fn sort_key(&self) -> SortKey {
match *self {
Self::Leaf(ref l) => l.sort_key(),
Self::Anchor(..) | Self::AnchorSize(..) => SortKey::Px,
_ => 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 node into another using the sum, that is, perform `x` + `y`.
pub 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 merge one node into another using the product, that is, perform `x` * `y`.
pub fn try_product_in_place(&mut self, other: &mut Self) -> bool {
if let Ok(resolved) = other.resolve() {
if let Some(number) = resolved.as_number() {
if number == 1.0 {
return true;
}
if self.is_product_distributive() {
if self.map(|v| v * number).is_err() {
return false;
}
return true;
}
}
}
if let Ok(resolved) = self.resolve() {
if let Some(number) = resolved.as_number() {
if number == 1.0 {
std::mem::swap(self, other);
return true;
}
if other.is_product_distributive() {
if other.map(|v| v * number).is_err() {
return false;
}
std::mem::swap(self, other);
return true;
}
}
}
false
}
/// Tries to apply a generic arithmetic 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(()),
}
}
/// Map the value of this node with the given operation.
pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
fn map_internal<L: CalcNodeLeaf>(
node: &mut CalcNode<L>,
op: &mut impl FnMut(f32) -> f32,
) -> Result<(), ()> {
match node {
CalcNode::Leaf(l) => l.map(op),
CalcNode::Negate(v) | CalcNode::Invert(v) => map_internal(v, op),
CalcNode::Sum(children) | CalcNode::Product(children) => {
for node in &mut **children {
map_internal(node, op)?;
}
Ok(())
},
CalcNode::MinMax(children, _) => {
for node in &mut **children {
map_internal(node, op)?;
}
Ok(())
},
CalcNode::Clamp { min, center, max } => {
map_internal(min, op)?;
map_internal(center, op)?;
map_internal(max, op)
},
CalcNode::Round { value, step, .. } => {
map_internal(value, op)?;
map_internal(step, op)
},
CalcNode::ModRem {
dividend, divisor, ..
} => {
map_internal(dividend, op)?;
map_internal(divisor, op)
},
CalcNode::Hypot(children) => {
for node in &mut **children {
map_internal(node, op)?;
}
Ok(())
},
CalcNode::Abs(child) | CalcNode::Sign(child) => map_internal(child, op),
// It is invalid to treat inner `CalcNode`s here - `anchor(--foo 50%) / 2` != `anchor(--foo 25%)`.
// Same applies to fallback, as we don't know if it will be used. Similar reasoning applies to `anchor-size()`.
CalcNode::Anchor(_) | CalcNode::AnchorSize(_) => Err(()),
// Trig functions are nonlinear: 2 * sin(x) != sin(2*x).
// Similarly for pow/sqrt/log/exp.
CalcNode::Sin(_)
| CalcNode::Cos(_)
| CalcNode::Tan(_)
| CalcNode::Asin(_)
| CalcNode::Acos(_)
| CalcNode::Atan(_)
| CalcNode::Atan2(..)
| CalcNode::Pow(..)
| CalcNode::Sqrt(_)
| CalcNode::Log(..)
| CalcNode::Exp(_) => Err(()),
}
}
map_internal(self, &mut op)
}
/// 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::Negate(ref c) => CalcNode::Negate(Box::new(c.map_leaves_internal(map))),
Self::Invert(ref c) => CalcNode::Invert(Box::new(c.map_leaves_internal(map))),
Self::Sum(ref c) => CalcNode::Sum(map_children(c, map)),
Self::Product(ref c) => CalcNode::Product(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,
}
},
Self::Sin(ref c) => CalcNode::Sin(Box::new(c.map_leaves_internal(map))),
Self::Cos(ref c) => CalcNode::Cos(Box::new(c.map_leaves_internal(map))),
Self::Tan(ref c) => CalcNode::Tan(Box::new(c.map_leaves_internal(map))),
Self::Asin(ref c) => CalcNode::Asin(Box::new(c.map_leaves_internal(map))),
Self::Acos(ref c) => CalcNode::Acos(Box::new(c.map_leaves_internal(map))),
Self::Atan(ref c) => CalcNode::Atan(Box::new(c.map_leaves_internal(map))),
Self::Atan2(ref a, ref b) => CalcNode::Atan2(
Box::new(a.map_leaves_internal(map)),
Box::new(b.map_leaves_internal(map)),
),
Self::Pow(ref a, ref b) => CalcNode::Pow(
Box::new(a.map_leaves_internal(map)),
Box::new(b.map_leaves_internal(map)),
),
Self::Sqrt(ref c) => CalcNode::Sqrt(Box::new(c.map_leaves_internal(map))),
Self::Hypot(ref c) => CalcNode::Hypot(map_children(c, map)),
Self::Log(ref a, ref b) => CalcNode::Log(
Box::new(a.map_leaves_internal(map)),
b.as_ref()
.map(|b| Box::new(b.map_leaves_internal(map)))
.into(),
),
Self::Exp(ref c) => CalcNode::Exp(Box::new(c.map_leaves_internal(map))),
Self::Abs(ref c) => CalcNode::Abs(Box::new(c.map_leaves_internal(map))),
Self::Sign(ref c) => CalcNode::Sign(Box::new(c.map_leaves_internal(map))),
Self::Anchor(ref f) => CalcNode::Anchor(Box::new(GenericAnchorFunction {
target_element: f.target_element.clone(),
side: match &f.side {
GenericAnchorSide::Keyword(k) => GenericAnchorSide::Keyword(*k),
GenericAnchorSide::Percentage(p) => {
GenericAnchorSide::Percentage(Box::new(p.map_leaves_internal(map)))
},
},
fallback: f
.fallback
.as_ref()
.map(|fb| {
Box::new(GenericAnchorFunctionFallback::new(
fb.is_calc_node,
fb.node.map_leaves_internal(map),
))
})
.into(),
})),
Self::AnchorSize(ref f) => CalcNode::AnchorSize(Box::new(GenericAnchorSizeFunction {
target_element: f.target_element.clone(),
size: f.size,
fallback: f
.fallback
.as_ref()
.map(|fb| {
Box::new(GenericAnchorFunctionFallback::new(
fb.is_calc_node,
fb.node.map_leaves_internal(map),
))
})
.into(),
})),
}
}
/// Resolve this node into a value.
pub fn resolve(&self) -> Result<L, ()> {
self.resolve_map(|l| Ok(l.clone()))
}
/// Resolve this node into a value, given a function that maps the leaf values.
pub fn resolve_map<F>(&self, mut leaf_to_output_fn: F) -> Result<L, ()>
where
F: FnMut(&L) -> Result<L, ()>,
{
self.resolve_internal(&mut leaf_to_output_fn)
}
fn resolve_internal<F>(&self, leaf_to_output_fn: &mut F) -> Result<L, ()>
where
F: FnMut(&L) -> Result<L, ()>,
{
match self {
Self::Leaf(l) => leaf_to_output_fn(l),
Self::Negate(child) => {
let mut result = child.resolve_internal(leaf_to_output_fn)?;
result.map(|v| v.neg())?;
Ok(result)
},
Self::Invert(child) => {
let mut result = child.resolve_internal(leaf_to_output_fn)?;
result.map(|v| 1.0 / v)?;
Ok(result)
},
Self::Sum(children) => {
let mut result = children[0].resolve_internal(leaf_to_output_fn)?;
for child in children.iter().skip(1) {
let right = child.resolve_internal(leaf_to_output_fn)?;
// try_op will make sure we only sum leaves with the same type.
result = result.try_op(&right, |left, right| left + right)?;
}
Ok(result)
},
Self::Product(children) => {
let mut result = children[0].resolve_internal(leaf_to_output_fn)?;
for child in children.iter().skip(1) {
let right = child.resolve_internal(leaf_to_output_fn)?;
// Mutliply only allowed when either side is a number.
match result.as_number() {
Some(left) => {
// Left side is a number, so we use the right node as the result.
result = right;
result.map(|v| v * left)?;
},
None => {
// Left side is not a number, so check if the right side is.
match right.as_number() {
Some(right) => {
result.map(|v| v * right)?;
},
None => {
// Multiplying with both sides having units.
return Err(());
},
}
},
}
}
Ok(result)
},
Self::MinMax(children, op) => {
let mut result = children[0].resolve_internal(leaf_to_output_fn)?;
if result.is_nan()? {
return Ok(result);
}
for child in children.iter().skip(1) {
let candidate = child.resolve_internal(leaf_to_output_fn)?;
// Leaf types must match for each child.
if !result.is_same_unit_as(&candidate) {
return Err(());
}
if candidate.is_nan()? {
result = candidate;
break;
}
let candidate_wins = match op {
MinMaxOp::Min => candidate.lt(&result, PositivePercentageBasis::Yes),
MinMaxOp::Max => candidate.gt(&result, PositivePercentageBasis::Yes),
};
if candidate_wins {
result = candidate;
}
}
Ok(result)
},
Self::Clamp { min, center, 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)?;
if !min.is_same_unit_as(&center) || !max.is_same_unit_as(&center) {
return Err(());
}
if min.is_nan()? {
return Ok(min);
}
if center.is_nan()? {
return Ok(center);
}
if max.is_nan()? {
return Ok(max);
}
let mut result = center;
if result.gt(&max, PositivePercentageBasis::Yes) {
result = max;
}
if result.lt(&min, PositivePercentageBasis::Yes) {
result = min
}
Ok(result)
},
Self::Round {
strategy,
value,
step,
} => {
let mut value = value.resolve_internal(leaf_to_output_fn)?;
let step = step.resolve_internal(leaf_to_output_fn)?;
if !value.is_same_unit_as(&step) {
return Err(());
}
let Some(step) = step.unitless_value() else {
return Err(());
};
let step = step.abs();
value.map(|value| {
// 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 f32::NAN;
}
if value.is_infinite() {
if step.is_infinite() {
return f32::NAN;
}
return value;
}
if step.is_infinite() {
match strategy {
RoundingStrategy::Nearest | RoundingStrategy::ToZero => {
return if value.is_sign_negative() { -0.0 } else { 0.0 }
},
RoundingStrategy::Up => {
return if !value.is_sign_negative() && !value.is_zero() {
f32::INFINITY
} else if !value.is_sign_negative() && value.is_zero() {
value
} else {
-0.0
}
},
RoundingStrategy::Down => {
return if value.is_sign_negative() && !value.is_zero() {
-f32::INFINITY
} else if value.is_sign_negative() && value.is_zero() {
value
} else {
0.0
}
},
}
}
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
}
},
}
})?;
Ok(value)
},
Self::ModRem {
dividend,
divisor,
op,
} => {
let mut dividend = dividend.resolve_internal(leaf_to_output_fn)?;
let divisor = divisor.resolve_internal(leaf_to_output_fn)?;
if !dividend.is_same_unit_as(&divisor) {
return Err(());
}
let Some(divisor) = divisor.unitless_value() else {
return Err(());
};
dividend.map(|dividend| op.apply(dividend, divisor))?;
Ok(dividend)
},
Self::Sin(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
let radians = result.as_number_or_angle_radians().ok_or(())?;
Ok(L::new_number(radians.sin()))
},
Self::Cos(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
let radians = result.as_number_or_angle_radians().ok_or(())?;
Ok(L::new_number(radians.cos()))
},
Self::Tan(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
let radians = result.as_number_or_angle_radians().ok_or(())?;
Ok(L::new_number(radians.tan()))
},
Self::Asin(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
let value = result.as_number().ok_or(())?;
Ok(L::new_angle_from_radians(value.asin()))
},
Self::Acos(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
let value = result.as_number().ok_or(())?;
Ok(L::new_angle_from_radians(value.acos()))
},
Self::Atan(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
let value = result.as_number().ok_or(())?;
Ok(L::new_angle_from_radians(value.atan()))
},
Self::Atan2(ref a, ref b) => {
let a = a.resolve_internal(leaf_to_output_fn)?;
let b = b.resolve_internal(leaf_to_output_fn)?;
if !a.is_same_unit_as(&b) {
return Err(());
}
let a_val = a.unitless_value().ok_or(())?;
let b_val = b.unitless_value().ok_or(())?;
Ok(L::new_angle_from_radians(a_val.atan2(b_val)))
},
Self::Pow(ref a, ref b) => {
let a = a.resolve_internal(leaf_to_output_fn)?;
let b = b.resolve_internal(leaf_to_output_fn)?;
let a_val = a.as_number().ok_or(())?;
let b_val = b.as_number().ok_or(())?;
Ok(L::new_number(a_val.powf(b_val)))
},
Self::Sqrt(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
let value = result.as_number().ok_or(())?;
Ok(L::new_number(value.sqrt()))
},
Self::Hypot(children) => {
let mut result = children[0].resolve_internal(leaf_to_output_fn)?;
result.map(|v| v.powi(2))?;
for child in children.iter().skip(1) {
let child_value = child.resolve_internal(leaf_to_output_fn)?;
if !result.is_same_unit_as(&child_value) {
return Err(());
}
let Some(child_value) = child_value.unitless_value() else {
return Err(());
};
result.map(|v| v + child_value.powi(2))?;
}
result.map(|v| v.sqrt())?;
Ok(result)
},
Self::Log(ref a, ref b) => {
let a = a.resolve_internal(leaf_to_output_fn)?;
let a_val = a.as_number().ok_or(())?;
let result = match b {
Optional::Some(ref b) => {
let b = b.resolve_internal(leaf_to_output_fn)?;
let b_val = b.as_number().ok_or(())?;
a_val.log(b_val)
},
Optional::None => a_val.ln(),
};
Ok(L::new_number(result))
},
Self::Exp(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
let value = result.as_number().ok_or(())?;
Ok(L::new_number(value.exp()))
},
Self::Abs(ref c) => {
let mut result = c.resolve_internal(leaf_to_output_fn)?;
result.map(|v| v.abs())?;
Ok(result)
},
Self::Sign(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
Ok(L::sign_from(&result)?)
},
Self::Anchor(_) | Self::AnchorSize(_) => Err(()),
}
}
/// Mutate nodes within this calc node tree using given the mapping function.
pub fn map_node<F>(&mut self, mut mapping_fn: F) -> Result<(), ()>
where
F: FnMut(&CalcNode<L>) -> Result<Option<CalcNode<L>>, ()>,
{
self.map_node_internal(&mut mapping_fn)
}
fn map_node_internal<F>(&mut self, mapping_fn: &mut F) -> Result<(), ()>
where
F: FnMut(&CalcNode<L>) -> Result<Option<CalcNode<L>>, ()>,
{
if let Some(node) = mapping_fn(self)? {
*self = node;
// Assume that any sub-nodes don't need to be mutated.
return Ok(());
}
match self {
Self::Leaf(_) | Self::Anchor(_) | Self::AnchorSize(_) => (),
Self::Negate(child)
| Self::Invert(child)
| Self::Abs(child)
| Self::Sign(child)
| Self::Sin(child)
| Self::Cos(child)
| Self::Tan(child)
| Self::Asin(child)
| Self::Acos(child)
| Self::Atan(child)
| Self::Sqrt(child)
| Self::Exp(child) => {
child.map_node_internal(mapping_fn)?;
},
Self::Atan2(a, b) => {
a.map_node_internal(mapping_fn)?;
b.map_node_internal(mapping_fn)?;
},
Self::Pow(a, b) => {
a.map_node_internal(mapping_fn)?;
b.map_node_internal(mapping_fn)?;
},
Self::Log(a, b) => {
a.map_node_internal(mapping_fn)?;
if let Optional::Some(b) = b {
b.map_node_internal(mapping_fn)?;
}
},
Self::Sum(children)
| Self::Product(children)
| Self::Hypot(children)
| Self::MinMax(children, _) => {
for child in children.iter_mut() {
child.map_node_internal(mapping_fn)?;
}
},
Self::Clamp { min, center, max } => {
min.map_node_internal(mapping_fn)?;
center.map_node_internal(mapping_fn)?;
max.map_node_internal(mapping_fn)?;
},
Self::Round { value, step, .. } => {
value.map_node_internal(mapping_fn)?;
step.map_node_internal(mapping_fn)?;
},
Self::ModRem {
dividend, divisor, ..
} => {
dividend.map_node_internal(mapping_fn)?;
divisor.map_node_internal(mapping_fn)?;
},
};
Ok(())
}
fn is_negative_leaf(&self) -> Result<bool, ()> {
Ok(match *self {
Self::Leaf(ref l) => l.is_negative()?,
_ => false,
})
}
fn is_zero_leaf(&self) -> Result<bool, ()> {
Ok(match *self {
Self::Leaf(ref l) => l.is_zero()?,
_ => false,
})
}
fn is_infinite_leaf(&self) -> Result<bool, ()> {
Ok(match *self {
Self::Leaf(ref l) => l.is_infinite()?,
_ => false,
})
}
fn is_nan_leaf(&self) -> Result<bool, ()> {
Ok(match *self {
Self::Leaf(ref l) => l.is_nan()?,
_ => false,
})
}
/// 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::Product(ref mut children)
| Self::MinMax(ref mut children, _)
| Self::Hypot(ref mut children) => {
for child in &mut **children {
child.visit_depth_first_internal(f);
}
},
Self::Negate(ref mut value) | Self::Invert(ref mut value) => {
value.visit_depth_first_internal(f);
},
Self::Sin(ref mut value)
| Self::Cos(ref mut value)
| Self::Tan(ref mut value)
| Self::Asin(ref mut value)
| Self::Acos(ref mut value)
| Self::Atan(ref mut value)
| Self::Sqrt(ref mut value)
| Self::Exp(ref mut value) => {
value.visit_depth_first_internal(f);
},
Self::Atan2(ref mut a, ref mut b) => {
a.visit_depth_first_internal(f);
b.visit_depth_first_internal(f);
},
Self::Pow(ref mut a, ref mut b) => {
a.visit_depth_first_internal(f);
b.visit_depth_first_internal(f);
},
Self::Log(ref mut a, ref mut b) => {
a.visit_depth_first_internal(f);
if let Optional::Some(b) = b {
b.visit_depth_first_internal(f);
}
},
Self::Abs(ref mut value) | Self::Sign(ref mut value) => {
value.visit_depth_first_internal(f);
},
Self::Leaf(..) | Self::Anchor(..) | Self::AnchorSize(..) => {},
}
f(self);
}
/// This function simplifies and sorts the calculation of the specified node. It simplifies
/// directly nested nodes while assuming that all nodes below it have already been simplified.
/// It is recommended to use this function in combination with `visit_depth_first()`.
///
/// This function is necessary only if the node needs to be preserved after parsing,
/// specifically for `<length-percentage>` cases where the calculation contains percentages or
/// relative units. Otherwise, the node can be evaluated using `resolve()`, which will
/// automatically provide a simplified value.
///
pub fn simplify_and_sort_direct_children(&mut self) -> SimplificationResult {
macro_rules! replace_self_with {
($slot:expr) => {{
let result = mem::replace($slot, Self::dummy());
*self = result;
}};
}
macro_rules! value_or_stop {
($op:expr) => {{
match $op {
Ok(value) => value,
Err(_) => return SimplificationResult::Unchanged,
}
}};
}
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.compare(&center, PositivePercentageBasis::Unknown) {
Some(o) => o,
None => return SimplificationResult::Unchanged,
};
// 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) {
replace_self_with!(&mut **min);
return SimplificationResult::Simplified;
}
// Otherwise try with max.
let max_cmp_center = match max.compare(&center, PositivePercentageBasis::Unknown) {
Some(o) => o,
None => return SimplificationResult::Unchanged,
};
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.compare(&min, PositivePercentageBasis::Unknown) {
Some(o) => o,
None => return SimplificationResult::Unchanged,
};
if matches!(max_cmp_min, cmp::Ordering::Less) {
replace_self_with!(&mut **min);
return SimplificationResult::Simplified;
}
replace_self_with!(&mut **max);
return SimplificationResult::Simplified;
}
// Otherwise we're the center node.
replace_self_with!(&mut **center);
return SimplificationResult::Simplified;
},
Self::Round {
strategy,
ref mut value,
ref mut step,
} => {
if value_or_stop!(step.is_zero_leaf()) {
value_or_stop!(value.coerce_to_value(f32::NAN));
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
}
if value_or_stop!(value.is_infinite_leaf())
&& value_or_stop!(step.is_infinite_leaf())
{
value_or_stop!(value.coerce_to_value(f32::NAN));
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
}
if value_or_stop!(value.is_infinite_leaf()) {
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
}
if value_or_stop!(step.is_infinite_leaf()) {
match strategy {
RoundingStrategy::Nearest | RoundingStrategy::ToZero => {
value_or_stop!(value.coerce_to_value(0.0));
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
},
RoundingStrategy::Up => {
if !value_or_stop!(value.is_negative_leaf())
&& !value_or_stop!(value.is_zero_leaf())
{
value_or_stop!(value.coerce_to_value(f32::INFINITY));
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
} else if !value_or_stop!(value.is_negative_leaf())
&& value_or_stop!(value.is_zero_leaf())
{
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
} else {
value_or_stop!(value.coerce_to_value(0.0));
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
}
},
RoundingStrategy::Down => {
if value_or_stop!(value.is_negative_leaf())
&& !value_or_stop!(value.is_zero_leaf())
{
value_or_stop!(value.coerce_to_value(-f32::INFINITY));
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
} else if value_or_stop!(value.is_negative_leaf())
&& value_or_stop!(value.is_zero_leaf())
{
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
} else {
value_or_stop!(value.coerce_to_value(0.0));
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
}
},
}
}
if value_or_stop!(step.is_negative_leaf()) {
step.negate();
}
let remainder = value_or_stop!(value.try_op(step, Rem::rem));
if value_or_stop!(remainder.is_zero_leaf()) {
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
}
let (mut lower_bound, mut upper_bound) = if value_or_stop!(value.is_negative_leaf())
{
let upper_bound = value_or_stop!(value.try_op(&remainder, Sub::sub));
let lower_bound = value_or_stop!(upper_bound.try_op(&step, Sub::sub));
(lower_bound, upper_bound)
} else {
let lower_bound = value_or_stop!(value.try_op(&remainder, Sub::sub));
let upper_bound = value_or_stop!(lower_bound.try_op(&step, Add::add));
(lower_bound, upper_bound)
};
match strategy {
RoundingStrategy::Nearest => {
let lower_diff = value_or_stop!(value.try_op(&lower_bound, Sub::sub));
let upper_diff = value_or_stop!(upper_bound.try_op(value, Sub::sub));
// In case of a tie, use the upper bound
if lower_diff.lt(&upper_diff, PositivePercentageBasis::Unknown) {
replace_self_with!(&mut lower_bound);
} else {
replace_self_with!(&mut upper_bound);
}
},
RoundingStrategy::Up => {
replace_self_with!(&mut upper_bound);
},
RoundingStrategy::Down => {
replace_self_with!(&mut lower_bound);
},
RoundingStrategy::ToZero => {
let mut lower_diff = lower_bound.clone();
let mut upper_diff = upper_bound.clone();
if value_or_stop!(lower_diff.is_negative_leaf()) {
lower_diff.negate();
}
if value_or_stop!(upper_diff.is_negative_leaf()) {
upper_diff.negate();
}
// In case of a tie, use the upper bound
if lower_diff.lt(&upper_diff, PositivePercentageBasis::Unknown) {
replace_self_with!(&mut lower_bound);
} else {
replace_self_with!(&mut upper_bound);
}
},
};
return SimplificationResult::Simplified;
},
Self::ModRem {
ref dividend,
ref divisor,
op,
} => {
let mut result = value_or_stop!(dividend.try_op(divisor, |a, b| op.apply(a, b)));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
},
Self::MinMax(ref mut children, op) => {
let winning_order = match op {
MinMaxOp::Min => cmp::Ordering::Less,
MinMaxOp::Max => cmp::Ordering::Greater,
};
if value_or_stop!(children[0].is_nan_leaf()) {
replace_self_with!(&mut children[0]);
return SimplificationResult::Simplified;
}
let mut result = 0;
for i in 1..children.len() {
if value_or_stop!(children[i].is_nan_leaf()) {
replace_self_with!(&mut children[i]);
return SimplificationResult::Simplified;
}
let o = match children[i]
.compare(&children[result], PositivePercentageBasis::Unknown)
{
// 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,
None => return SimplificationResult::Unchanged,
Some(o) => o,
};
if o == winning_order {
result = i;
}
}
replace_self_with!(&mut children[result]);
return SimplificationResult::Simplified;
},
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 {
replace_self_with!(&mut children_slot[0]);
return SimplificationResult::Simplified;
}
let mut children = mem::take(children_slot).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
children.extend(kid_children.into_vec());
}
}
let children_len = children.len();
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());
let updated_children_len = children.len();
if updated_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();
}
return if updated_children_len != children_len {
SimplificationResult::Simplified
} else {
SimplificationResult::Unchanged
};
},
Self::Product(ref mut children_slot) => {
let mut products_to_merge = SmallVec::<[_; 3]>::new();
let mut extra_kids = 0;
for (i, child) in children_slot.iter().enumerate() {
if let Self::Product(ref children) = *child {
extra_kids += children.len();
products_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 product already or not,
// so lift it up and continue.
if children_slot.len() == 1 {
replace_self_with!(&mut children_slot[0]);
return SimplificationResult::Unchanged;
}
let mut children = mem::take(children_slot).into_vec();
if !products_to_merge.is_empty() {
children.reserve(extra_kids - products_to_merge.len());
// Merge all our nested sums, in reverse order so that the
// list indices are not invalidated.
for i in products_to_merge.drain(..).rev() {
let kid_children = match children.swap_remove(i) {
Self::Product(c) => c,
_ => unreachable!(),
};
// This would be nicer with
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(|right, left| left.try_product_in_place(right));
if children.len() == 1 {
// If only one children remains, lift it up, and carry on.
replace_self_with!(&mut children[0]);
return SimplificationResult::Simplified;
} else {
// Else put our simplified children back.
*children_slot = children.into_boxed_slice().into();
}
return SimplificationResult::Unchanged;
},
Self::Sin(ref mut child) => {
if let CalcNode::Leaf(ref leaf) = **child {
if let Some(radians) = leaf.as_number_or_angle_radians() {
let mut result = Self::Leaf(L::new_number(radians.sin()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Cos(ref mut child) => {
if let CalcNode::Leaf(ref leaf) = **child {
if let Some(radians) = leaf.as_number_or_angle_radians() {
let mut result = Self::Leaf(L::new_number(radians.cos()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Tan(ref mut child) => {
if let CalcNode::Leaf(ref leaf) = **child {
if let Some(radians) = leaf.as_number_or_angle_radians() {
let mut result = Self::Leaf(L::new_number(radians.tan()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Asin(ref mut child) => {
if let CalcNode::Leaf(ref leaf) = **child {
if let Some(value) = leaf.as_number() {
let mut result = Self::Leaf(L::new_angle_from_radians(value.asin()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Acos(ref mut child) => {
if let CalcNode::Leaf(ref leaf) = **child {
if let Some(value) = leaf.as_number() {
let mut result = Self::Leaf(L::new_angle_from_radians(value.acos()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Atan(ref mut child) => {
if let CalcNode::Leaf(ref leaf) = **child {
if let Some(value) = leaf.as_number() {
let mut result = Self::Leaf(L::new_angle_from_radians(value.atan()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Atan2(ref mut a, ref mut b) => {
if let (CalcNode::Leaf(ref la), CalcNode::Leaf(ref lb)) = (&**a, &**b) {
if la.is_same_unit_as(lb) {
if let (Some(a_val), Some(b_val)) =
(la.unitless_value(), lb.unitless_value())
{
let mut result =
Self::Leaf(L::new_angle_from_radians(a_val.atan2(b_val)));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
}
return SimplificationResult::Unchanged;
},
Self::Pow(ref mut a, ref mut b) => {
if let (CalcNode::Leaf(ref la), CalcNode::Leaf(ref lb)) = (&**a, &**b) {
if let (Some(a_val), Some(b_val)) = (la.as_number(), lb.as_number()) {
let mut result = Self::Leaf(L::new_number(a_val.powf(b_val)));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Sqrt(ref mut child) => {
if let CalcNode::Leaf(ref leaf) = **child {
if let Some(value) = leaf.as_number() {
let mut result = Self::Leaf(L::new_number(value.sqrt()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Hypot(ref children) => {
let mut result = value_or_stop!(children[0].try_op(&children[0], Mul::mul));
for child in children.iter().skip(1) {
let square = value_or_stop!(child.try_op(&child, Mul::mul));
result = value_or_stop!(result.try_op(&square, Add::add));
}
result = value_or_stop!(result.try_op(&result, |a, _| a.sqrt()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
},
Self::Log(ref mut a, ref mut b) => {
if let CalcNode::Leaf(ref la) = **a {
if let Some(a_val) = la.as_number() {
let folded = match b {
Optional::Some(ref b) => {
if let CalcNode::Leaf(ref lb) = **b {
lb.as_number().map(|b_val| a_val.log(b_val))
} else {
None
}
},
Optional::None => Some(a_val.ln()),
};
if let Some(number) = folded {
let mut result = Self::Leaf(L::new_number(number));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
}
return SimplificationResult::Unchanged;
},
Self::Exp(ref mut child) => {
if let CalcNode::Leaf(ref leaf) = **child {
if let Some(value) = leaf.as_number() {
let mut result = Self::Leaf(L::new_number(value.exp()));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
}
return SimplificationResult::Unchanged;
},
Self::Abs(ref mut child) => {
if let CalcNode::Leaf(leaf) = child.as_mut() {
value_or_stop!(leaf.map(|v| v.abs()));
replace_self_with!(&mut **child);
return SimplificationResult::Simplified;
}
return SimplificationResult::Unchanged;
},
Self::Sign(ref mut child) => {
if let CalcNode::Leaf(leaf) = child.as_mut() {
let mut result = Self::Leaf(value_or_stop!(L::sign_from(leaf)));
replace_self_with!(&mut result);
return SimplificationResult::Simplified;
}
return SimplificationResult::Unchanged;
},
Self::Negate(ref mut child) => {
// Step 6.
match &mut **child {
CalcNode::Leaf(_) => {
// 1. If root’s child is a numeric value, return an equivalent numeric value, but
// with the value negated (0 - value).
child.negate();
replace_self_with!(&mut **child);
return SimplificationResult::Simplified;
},
CalcNode::Negate(value) => {
// 2. If root’s child is a Negate node, return the child’s child.
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
},
_ => {
// 3. Return root.
return SimplificationResult::Unchanged;
},
}
},
Self::Invert(ref mut child) => {
// Step 7.
match &mut **child {
CalcNode::Leaf(leaf) => {
// 1. If root’s child is a number (not a percentage or dimension) return the
// reciprocal of the child’s value.
if leaf.unit().is_empty() {
value_or_stop!(child.map(|v| 1.0 / v));
replace_self_with!(&mut **child);
return SimplificationResult::Simplified;
}
return SimplificationResult::Unchanged;
},
CalcNode::Invert(value) => {
// 2. If root’s child is an Invert node, return the child’s child.
replace_self_with!(&mut **value);
return SimplificationResult::Simplified;
},
_ => {
// 3. Return root.
return SimplificationResult::Unchanged;
},
}
},
Self::Leaf(ref mut l) => {
return l.simplify();
},
Self::Anchor(ref mut f) => {
if let GenericAnchorSide::Percentage(ref mut n) = f.side {
n.simplify_and_sort();
return SimplificationResult::Simplified;
}
if let Some(fallback) = f.fallback.as_mut() {
return fallback.node.simplify_and_sort();
}
return SimplificationResult::Unchanged;
},
Self::AnchorSize(ref mut f) => {
if let Some(fallback) = f.fallback.as_mut() {
return fallback.node.simplify_and_sort();
}
return SimplificationResult::Unchanged;
},
}
}
/// Simplifies and sorts the kids in the whole calculation subtree.
pub fn simplify_and_sort(&mut self) -> SimplificationResult {
let mut res = SimplificationResult::Unchanged;
self.visit_depth_first(|node| match node.simplify_and_sort_direct_children() {
SimplificationResult::Simplified => {
res = SimplificationResult::Simplified;
},
_ => {},
});
res
}
fn to_css_impl<W>(&self, dest: &mut CssWriter<W>, level: ArgumentLevel) -> 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
},
Self::Sin(_) => {
dest.write_str("sin(")?;
true
},
Self::Cos(_) => {
dest.write_str("cos(")?;
true
},
Self::Tan(_) => {
dest.write_str("tan(")?;
true
},
Self::Asin(_) => {
dest.write_str("asin(")?;
true
},
Self::Acos(_) => {
dest.write_str("acos(")?;
true
},
Self::Atan(_) => {
dest.write_str("atan(")?;
true
},
Self::Atan2(..) => {
dest.write_str("atan2(")?;
true
},
Self::Pow(..) => {
dest.write_str("pow(")?;
true
},
Self::Sqrt(_) => {
dest.write_str("sqrt(")?;
true
},
Self::Hypot(_) => {
dest.write_str("hypot(")?;
true
},
Self::Log(..) => {
dest.write_str("log(")?;
true
},
Self::Exp(_) => {
dest.write_str("exp(")?;
true
},
Self::Abs(_) => {
dest.write_str("abs(")?;
true
},
Self::Sign(_) => {
dest.write_str("sign(")?;
true
},
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::Invert(_) => {
if matches!(level, ArgumentLevel::CalculationRoot) {
dest.write_str("calc")?;
}
dest.write_str("(1 / ")?;
true
},
Self::Sum(_) | Self::Product(_) => match level {
ArgumentLevel::CalculationRoot => {
dest.write_str("calc(")?;
true
},
ArgumentLevel::ArgumentRoot => false,
ArgumentLevel::Nested => {
dest.write_str("(")?;
true
},
},
Self::Leaf(leaf) => match level {
ArgumentLevel::CalculationRoot => {
if leaf.should_serialize_with_root_calc_wrapper() {
dest.write_str("calc(")?;
true
} else {
false
}
},
ArgumentLevel::ArgumentRoot | ArgumentLevel::Nested => false,
},
Self::Anchor(_) | Self::AnchorSize(_) => false,
};
match *self {
Self::MinMax(ref children, _) | Self::Hypot(ref children) => {
let mut first = true;
for child in &**children {
if !first {
dest.write_str(", ")?;
}
first = false;
child.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
}
},
Self::Negate(ref value) | Self::Invert(ref value) => {
value.to_css_impl(dest, ArgumentLevel::Nested)?
},
Self::Sum(ref children) => {
let mut first = true;
for child in &**children {
if !first {
match child {
Self::Leaf(l) => {
if let Ok(true) = l.is_negative() {
dest.write_str(" - ")?;
let mut negated = l.clone();
// We can unwrap here, because we already
// checked if the value inside is negative.
negated.map(std::ops::Neg::neg).unwrap();
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, ArgumentLevel::Nested)?;
}
}
},
Self::Product(ref children) => {
let mut first = true;
for child in &**children {
if !first {
match child {
Self::Invert(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, ArgumentLevel::Nested)?;
}
}
},
Self::Clamp {
ref min,
ref center,
ref max,
} => {
min.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
center.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
max.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
},
Self::Round {
ref value,
ref step,
..
} => {
value.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
step.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
},
Self::ModRem {
ref dividend,
ref divisor,
..
} => {
dividend.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
divisor.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
},
Self::Sin(ref v)
| Self::Cos(ref v)
| Self::Tan(ref v)
| Self::Asin(ref v)
| Self::Acos(ref v)
| Self::Atan(ref v) => v.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?,
Self::Atan2(ref a, ref b) => {
a.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
b.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
},
Self::Pow(ref a, ref b) => {
a.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
dest.write_str(", ")?;
b.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
},
Self::Sqrt(ref v) | Self::Exp(ref v) => {
v.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?
},
Self::Log(ref a, ref b) => {
a.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
if let Optional::Some(ref b) = b {
dest.write_str(", ")?;
b.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?;
}
},
Self::Abs(ref v) | Self::Sign(ref v) => {
v.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?
},
Self::Leaf(ref l) => l.to_css(dest)?,
Self::Anchor(ref f) => f.to_css(dest)?,
Self::AnchorSize(ref f) => f.to_css(dest)?,
}
if write_closing_paren {
dest.write_char(')')?;
}
Ok(())
}
fn to_typed_impl(
&self,
dest: &mut ThinVec<TypedValue>,
level: ArgumentLevel,
) -> Result<(), ()> {
// Note: Naturally, only nodes that can be reified into CSSUnitValue
// and CSSMathValue objects are supported here:
// Leaf, Negate, Invert, Sum, Product, MinMax, and Clamp.
match *self {
Self::Leaf(ref l) => match l.to_typed_value() {
Some(TypedValue::Numeric(inner)) => {
match level {
ArgumentLevel::CalculationRoot => {
dest.push(TypedValue::Numeric(NumericValue::Math(MathValue::Sum(
ThinVec::from([inner]),
))));
},
ArgumentLevel::ArgumentRoot | ArgumentLevel::Nested => {
dest.push(TypedValue::Numeric(inner));
},
}
Ok(())
},
_ => Err(()),
},
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 reified.
debug_assert!(
false,
"We never reify Negate nodes as they are handled inside Sum nodes."
);
Err(())
},
Self::Invert(ref value) => {
let inner = CalcNodeWithLevel::nested(value)
.to_numeric_value()
.ok_or(())?;
dest.push(TypedValue::Numeric(NumericValue::Math(MathValue::Invert(
Box::new(inner),
))));
Ok(())
},
Self::Sum(ref children) => {
let mut values = ThinVec::new();
let mut first = true;
for child in &**children {
if !first {
match child {
Self::Leaf(l) => {
if let Ok(true) = l.is_negative() {
let mut negated = l.clone();
// We can unwrap here, because we already
// checked if the value inside is negative.
negated.map(std::ops::Neg::neg).unwrap();
let inner = negated.to_numeric_value().ok_or(())?;
values.push(NumericValue::Math(MathValue::Negate(Box::new(
inner,
))));
} else {
let inner = l.to_numeric_value().ok_or(())?;
values.push(inner);
}
},
Self::Negate(n) => {
let inner = CalcNodeWithLevel::nested(n.as_ref())
.to_numeric_value()
.ok_or(())?;
values.push(NumericValue::Math(MathValue::Negate(Box::new(inner))));
},
_ => {
let inner = CalcNodeWithLevel::nested(child)
.to_numeric_value()
.ok_or(())?;
values.push(inner);
},
}
} else {
first = false;
let inner = CalcNodeWithLevel::nested(child)
.to_numeric_value()
.ok_or(())?;
values.push(inner);
}
}
dest.push(TypedValue::Numeric(NumericValue::Math(MathValue::Sum(
values,
))));
Ok(())
},
Self::Product(ref children) => {
let mut values = ThinVec::new();
let mut first = true;
for child in &**children {
if !first {
match child {
Self::Invert(n) => {
let inner = CalcNodeWithLevel::nested(n.as_ref())
.to_numeric_value()
.ok_or(())?;
values.push(NumericValue::Math(MathValue::Invert(Box::new(inner))));
},
_ => {
let inner = CalcNodeWithLevel::nested(child)
.to_numeric_value()
.ok_or(())?;
values.push(inner);
},
}
} else {
first = false;
let inner = CalcNodeWithLevel::nested(child)
.to_numeric_value()
.ok_or(())?;
values.push(inner);
}
}
dest.push(TypedValue::Numeric(NumericValue::Math(MathValue::Product(
values,
))));
Ok(())
},
Self::MinMax(ref children, op) => {
let mut values = ThinVec::new();
for child in &**children {
let inner = CalcNodeWithLevel::argument_root(child)
.to_numeric_value()
.ok_or(())?;
values.push(inner);
}
let math_value = match op {
MinMaxOp::Min => MathValue::Min(values),
MinMaxOp::Max => MathValue::Max(values),
};
dest.push(TypedValue::Numeric(NumericValue::Math(math_value)));
Ok(())
},
Self::Clamp {
ref min,
ref center,
ref max,
} => {
let lower = CalcNodeWithLevel::argument_root(min)
.to_numeric_value()
.ok_or(())?;
let value = CalcNodeWithLevel::argument_root(center)
.to_numeric_value()
.ok_or(())?;
let upper = CalcNodeWithLevel::argument_root(max)
.to_numeric_value()
.ok_or(())?;
dest.push(TypedValue::Numeric(NumericValue::Math(MathValue::Clamp(
[lower, value, upper].into(),
))));
Ok(())
},
_ => Err(()),
}
}
fn compare(
&self,
other: &Self,
basis_positive: PositivePercentageBasis,
) -> Option<cmp::Ordering> {
match (self, other) {
(&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => {
one.compare(other, basis_positive)
},
_ => None,
}
}
compare_helpers!();
}
impl<L: CalcNodeLeaf> ToCss for CalcNode<L> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
self.to_css_impl(dest, ArgumentLevel::CalculationRoot)
}
}
impl<L: CalcNodeLeaf> ToTyped for CalcNode<L> {
fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
CalcNodeWithLevel::calculation_root(self).to_typed(dest)
}
}
struct CalcNodeWithLevel<'a, L> {
node: &'a CalcNode<L>,
level: ArgumentLevel,
}
impl<'a, L> CalcNodeWithLevel<'a, L> {
#[inline]
fn new(node: &'a CalcNode<L>, level: ArgumentLevel) -> Self {
Self { node, level }
}
#[inline]
fn calculation_root(node: &'a CalcNode<L>) -> Self {
Self::new(node, ArgumentLevel::CalculationRoot)
}
#[inline]
fn argument_root(node: &'a CalcNode<L>) -> Self {
Self::new(node, ArgumentLevel::ArgumentRoot)
}
#[inline]
fn nested(node: &'a CalcNode<L>) -> Self {
Self::new(node, ArgumentLevel::Nested)
}
}
impl<'a, L: CalcNodeLeaf> ToTyped for CalcNodeWithLevel<'a, L> {
fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
self.node.to_typed_impl(dest, self.level.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_sum_with_checks() {
assert!(CalcUnits::LENGTH.can_sum_with(CalcUnits::LENGTH));
assert!(CalcUnits::LENGTH.can_sum_with(CalcUnits::PERCENTAGE));
assert!(CalcUnits::LENGTH.can_sum_with(CalcUnits::LENGTH_PERCENTAGE));
assert!(CalcUnits::PERCENTAGE.can_sum_with(CalcUnits::LENGTH));
assert!(CalcUnits::PERCENTAGE.can_sum_with(CalcUnits::PERCENTAGE));
assert!(CalcUnits::PERCENTAGE.can_sum_with(CalcUnits::LENGTH_PERCENTAGE));
assert!(CalcUnits::LENGTH_PERCENTAGE.can_sum_with(CalcUnits::LENGTH));
assert!(CalcUnits::LENGTH_PERCENTAGE.can_sum_with(CalcUnits::PERCENTAGE));
assert!(CalcUnits::LENGTH_PERCENTAGE.can_sum_with(CalcUnits::LENGTH_PERCENTAGE));
assert!(!CalcUnits::ANGLE.can_sum_with(CalcUnits::TIME));
assert!(CalcUnits::ANGLE.can_sum_with(CalcUnits::ANGLE));
assert!(!(CalcUnits::ANGLE | CalcUnits::TIME).can_sum_with(CalcUnits::ANGLE));
assert!(!CalcUnits::ANGLE.can_sum_with(CalcUnits::ANGLE | CalcUnits::TIME));
assert!(
!(CalcUnits::ANGLE | CalcUnits::TIME).can_sum_with(CalcUnits::ANGLE | CalcUnits::TIME)
);
}
}