/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
usecrate::logical_geometry::PhysicalSide; usecrate::values::generics::box_::PositionProperty; usecrate::values::generics::length::GenericAnchorSizeFunction; usecrate::values::generics::position::{AnchorSide, GenericAnchorFunction}; use num_traits::Zero; use smallvec::SmallVec; use std::fmt::{self, Write}; use std::ops::{Add, Mul, Neg, Rem, Sub}; use std::{cmp, mem}; use style_traits::{CssWriter, ToCss};
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. // https://drafts.csswg.org/css-values/#round-infinities if matches!(self, Self::Mod) &&
divisor.is_infinite() &&
dividend.is_sign_negative() != divisor.is_sign_negative()
{ return f32::NAN;
}
/// The strategy used in `round()` #[derive(
Clone,
Copy,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
ToAnimatedZero,
ToResolvedValue,
ToShmem,
)] #[repr(u8)] pubenum 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)] pubenum SortKey {
Number,
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,
Px,
Rem,
Rlh,
Sec,
Svb,
Svh,
Svi,
Svmax,
Svmin,
Svw,
Vb,
Vh,
Vi,
Vmax,
Vmin,
Vw,
ColorComponent,
Other,
}
/// `anchor()` function used in math functions. pubtype GenericCalcAnchorFunction<L> =
GenericAnchorFunction<Box<GenericCalcNode<L>>, Box<GenericCalcNode<L>>>; /// `anchor-size()` function used in math functions. pubtype GenericCalcAnchorSizeFunction<L> = GenericAnchorSizeFunction<Box<GenericCalcNode<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 /// 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,
)] pubenum GenericCalcNode<L> { /// A leaf node.
Leaf(L), /// A node that negates its child, e.g. Negate(1) == -1.
Negate(Box<GenericCalcNode<L>>), /// 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<GenericCalcNode<L>>), /// A sum node, representing `a + b + c` where a, b, and c are the /// arguments.
Sum(crate::OwnedSlice<GenericCalcNode<L>>), /// A product node, representing `a * b * c` where a, b, and c are the /// arguments.
Product(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,
}, /// A `hypot()` function
Hypot(crate::OwnedSlice<GenericCalcNode<L>>), /// An `abs()` function.
Abs(Box<GenericCalcNode<L>>), /// A `sign()` function.
Sign(Box<GenericCalcNode<L>>), /// An `anchor()` function.
Anchor(Box<GenericCalcAnchorFunction<L>>), /// An `anchor-size()` function.
AnchorSize(Box<GenericCalcAnchorSizeFunction<L>>),
}
pubuseself::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)] pubstruct 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; /// A component of a color (r, g, b, h, s, l, alpha, etc.) const COLOR_COMPONENT = 1 << 5;
/// <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() | Self::COLOR_COMPONENT.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). pubenum 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 { matchself.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. pubtrait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { /// 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>;
/// 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>;
/// 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(&mutself, 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(&mutself, other: &mutSelf) -> bool;
/// Map the value of this node with the given operation. fn map(&mutself, op: impl FnMut(f32) -> f32) -> Result<(), ()>;
/// Canonicalizes the expression if necessary. fn simplify(&mutself);
/// 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(());
};
/// The level of any argument being serialized in `to_css_impl`. enum ArgumentLevel { /// The root of a calculation tree.
CalculationRoot, /// The root of an operand node's argument, e.g. `min(10, 20)`, `10` and `20` will have this /// level, but min in this case will have `TopMost`.
ArgumentRoot, /// Any other values serialized in the tree.
Nested,
}
/// Trait for resolving anchor positioning functions in math functions. pubtrait AnchorPositioningResolver<L: CalcNodeLeaf> { /// Resolve `anchor()` function to a value. fn resolve_anchor(
&self,
f: &GenericCalcAnchorFunction<L>,
side: PhysicalSide,
position: PositionProperty,
) -> Result<GenericCalcNode<L>, ()>;
/// Resolve `anchor-size()` function to a value. fn resolve_anchor_size(
&self,
f: &GenericCalcAnchorSizeFunction<L>,
position: PositionProperty,
) -> Result<GenericCalcNode<L>, ()>;
}
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(&mutself, 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] pubfn is_product_distributive(&self) -> bool { matchself { Self::Leaf(l) => l.unit() != CalcUnits::COLOR_COMPONENT, 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. pubfn unit(&self) -> Result<CalcUnits, ()> {
Ok(matchself {
CalcNode::Leaf(l) => l.unit(),
CalcNode::Negate(child) | CalcNode::Invert(child) | CalcNode::Abs(child) => {
child.unit()?
},
CalcNode::Sum(children) => { letmut 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. letmut 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) => { letmut 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
}
})
}
/// Negate the node inline. If the node is distributive, it is replaced by the result, /// otherwise the node is wrapped in a [`Negate`] node. pubfn negate(&mutself) { /// 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(refmut leaf) => { if leaf.map(std::ops::Neg::neg).is_err() {
wrap_self_in_negate(self)
}
},
CalcNode::Negate(refmut 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(refmut children) => { for child in children.iter_mut() {
child.negate();
}
},
CalcNode::Product(_) => { // -(2 * 3 / 4) == -(1.5)
wrap_self_in_negate(self);
},
CalcNode::MinMax(refmut children, refmut 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 { refmut min, refmut center, refmut 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 { refmut strategy, refmut value, refmut 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 { refmut dividend, refmut divisor,
..
} => {
dividend.negate();
divisor.negate();
},
CalcNode::Hypot(refmut children) => { for child in children.iter_mut() {
child.negate();
}
},
CalcNode::Abs(_) => {
wrap_self_in_negate(self);
},
CalcNode::Sign(refmut child) => {
child.negate();
},
CalcNode::Anchor(_) | CalcNode::AnchorSize(_) => {
wrap_self_in_negate(self);
},
}
}
/// Returns the leaf if we can (if simplification has allowed it). pubfn 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`. pubfn try_sum_in_place(&mutself, other: &Self) -> Result<(), ()> { match (self, other) {
(&mut CalcNode::Leaf(refmut 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`. pubfn try_product_in_place(&mutself, other: &mutSelf) -> bool { iflet Ok(resolved) = other.resolve() { iflet Some(number) = resolved.as_number() { if number == 1.0 { returntrue;
}
/// Map the value of this node with the given operation. pubfn map(&mutself, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> { fn map_internal<L: CalcNodeLeaf>(
node: &mut CalcNode<L>,
op: &mutimpl 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(())
}
}
map_internal(self, &mut op)
}
/// Convert this `CalcNode` into a `CalcNode` with a different leaf kind. pubfn 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::Hypot(ref c) => CalcNode::Hypot(map_children(c, 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 {
AnchorSide::Keyword(k) => AnchorSide::Keyword(*k),
AnchorSide::Percentage(p) => AnchorSide::Percentage(Box::new(p.map_leaves_internal(map))),
},
fallback: f.fallback.as_ref().map(|fb| Box::new(fb.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(fb.map_leaves_internal(map))).into(),
}
)),
}
}
/// Resolve this node into a value. pubfn resolve(&self) -> Result<L, ()> { self.resolve_map(|l| Ok(l.clone()), |_| Err(()))
}
/// Resolve this node into a value, given a function that maps the leaf values. pubfn resolve_map<F, NF>(&self, mut leaf_to_output_fn: F, mut node_mapping_fn: NF) -> Result<L, ()> where
F: FnMut(&L) -> Result<L, ()>,
NF: FnMut(&CalcNode<L>) -> Result<CalcNode<L>, ()>,
{ self.resolve_internal(&mut leaf_to_output_fn, &mut node_mapping_fn)
}
for child in children.iter().skip(1) { let right = child.resolve_internal(leaf_to_output_fn, node_mapping_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) => { letmut result = children[0].resolve_internal(leaf_to_output_fn, node_mapping_fn)?;
for child in children.iter().skip(1) { let right = child.resolve_internal(leaf_to_output_fn, node_mapping_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(());
},
}
},
}
}
for child in children.iter().skip(1) { let candidate = child.resolve_internal(leaf_to_output_fn, node_mapping_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, node_mapping_fn)?; let center = center.resolve_internal(leaf_to_output_fn, node_mapping_fn)?; let max = max.resolve_internal(leaf_to_output_fn, node_mapping_fn)?;
if !min.is_same_unit_as(¢er) || !max.is_same_unit_as(¢er) { return Err(());
}
if min.is_nan()? { return Ok(min);
}
if center.is_nan()? { return Ok(center);
}
if max.is_nan()? { return Ok(max);
}
letmut 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,
} => { letmut value = value.resolve_internal(leaf_to_output_fn, node_mapping_fn)?; let step = step.resolve_internal(leaf_to_output_fn, node_mapping_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;
}
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
}
},
}
})?;
/// 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. pubfn visit_depth_first(&mutself, mut f: impl FnMut(&'color:red'>mutSelf)) { self.visit_depth_first_internal(&mut f)
}
/// 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. /// /// <https://drafts.csswg.org/css-values-4/#calc-simplification> pubfn simplify_and_sort_direct_children(&mutself) {
macro_rules! replace_self_with {
($slot:expr) => {{ let result = mem::replace($slot, Self::dummy());
*self = result;
}};
}
match *self { Self::Clamp { refmut min, refmut center, refmut max,
} => { // NOTE: clamp() is max(min, min(center, max)) let min_cmp_center = match min.compare(¢er, PositivePercentageBasis::Unknown) {
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) {
replace_self_with!(&mut **min); return;
}
// Otherwise try with max. let max_cmp_center = match max.compare(¢er, PositivePercentageBasis::Unknown) {
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.compare(&min, PositivePercentageBasis::Unknown) {
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) {
replace_self_with!(&mut **min); return;
}
replace_self_with!(&mut **max); return;
}
// Otherwise we're the center node.
replace_self_with!(&mut **center);
}, Self::Round {
strategy, refmut value, refmut step,
} => { if value_or_stop!(step.is_zero_leaf()) {
value_or_stop!(value.coerce_to_value(f32::NAN));
replace_self_with!(&mut **value); return;
}
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;
}
if value_or_stop!(value.is_infinite_leaf()) {
replace_self_with!(&mut **value); return;
}
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;
}
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 => { letmut lower_diff = lower_bound.clone(); letmut 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);
}
},
};
}, Self::ModRem { ref dividend, ref divisor,
op,
} => { letmut result = value_or_stop!(dividend.try_op(divisor, |a, b| op.apply(a, b)));
replace_self_with!(&mut result);
}, Self::MinMax(refmut 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;
}
letmut result = 0; for i in1..children.len() { if value_or_stop!(children[i].is_nan_leaf()) {
replace_self_with!(&mut children[i]); return;
} 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, // see https://github.com/w3c/csswg-drafts/issues/4756
None => return,
Some(o) => o,
};
// 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;
}
letmut 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!(),
};
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::Product(refmut children_slot) => { letmut products_to_merge = SmallVec::<[_; 3]>::new(); letmut extra_kids = 0; for (i, child) in children_slot.iter().enumerate() { ifletSelf::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;
}
letmut 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!(),
};
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]);
} else { // Else put our simplified children back.
*children_slot = children.into_boxed_slice().into();
}
}, Self::Hypot(ref children) => { letmut 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);
}, Self::Abs(refmut child) => { iflet CalcNode::Leaf(leaf) = child.as_mut() {
value_or_stop!(leaf.map(|v| v.abs()));
replace_self_with!(&mut **child);
}
}, Self::Sign(refmut child) => { iflet CalcNode::Leaf(leaf) = child.as_mut() { letmut result = Self::Leaf(value_or_stop!(L::sign_from(leaf)));
replace_self_with!(&mut result);
}
}, Self::Negate(refmut 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);
},
CalcNode::Negate(value) => { // 2. If root’s child is a Negate node, return the child’s child.
replace_self_with!(&mut **value);
},
_ => { // 3. Return root.
},
}
}, Self::Invert(refmut 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);
}
},
CalcNode::Invert(value) => { // 2. If root’s child is an Invert node, return the child’s child.
replace_self_with!(&mut **value);
},
_ => { // 3. Return root.
},
}
}, Self::Leaf(refmut l) => {
l.simplify();
}, Self::Anchor(refmut f) => { iflet AnchorSide::Percentage(refmut n) = f.side {
n.simplify_and_sort();
} iflet Some(fallback) = f.fallback.as_mut() {
fallback.simplify_and_sort();
}
}, Self::AnchorSize(refmut f) => { iflet Some(fallback) = f.fallback.as_mut() {
fallback.simplify_and_sort();
}
}
}
}
/// Simplifies and sorts the kids in the whole calculation subtree. pubfn simplify_and_sort(&mutself) { self.visit_depth_first(|node| node.simplify_and_sort_direct_children())
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.