style: Implement CSS mod() and rem() functions

Differential Revision: https://phabricator.services.mozilla.com/D163166
This commit is contained in:
Connor Pearson 2022-11-29 13:05:39 +00:00 committed by Martin Robinson
parent 653b37f80a
commit e55c03c8ff
2 changed files with 152 additions and 1 deletions

View file

@ -34,6 +34,27 @@ pub enum MinMaxOp {
Max,
}
/// Whether we're a `mod` or `rem` function.
#[derive(
Clone,
Copy,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
ToAnimatedZero,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum ModRemOp {
/// `mod()`
Mod,
/// `rem()`
Rem,
}
/// The strategy used in `round()`
#[derive(
Clone,
@ -162,6 +183,15 @@ pub enum GenericCalcNode<L> {
/// The step value.
step: Box<GenericCalcNode<L>>,
},
/// A `mod()` or `rem()` function.
ModRem {
/// The dividend calculation.
dividend: Box<GenericCalcNode<L>>,
/// The divisor calculation.
divisor: Box<GenericCalcNode<L>>,
/// Is the function mod or rem?
op: ModRemOp,
},
}
pub use self::GenericCalcNode as CalcNode;
@ -314,6 +344,19 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
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,
}
},
}
}
@ -469,6 +512,29 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
},
}
},
Self::ModRem {
ref dividend,
ref divisor,
op,
} => {
let dividend = dividend.resolve_internal(leaf_to_output_fn)?;
let divisor = divisor.resolve_internal(leaf_to_output_fn)?;
// In mod(A, B) only, if B is infinite and A has opposite sign to B
// (including an oppositely-signed zero), the result is NaN.
// https://drafts.csswg.org/css-values/#round-infinities
if matches!(op, ModRemOp::Mod) &&
divisor.is_infinite() &&
dividend.is_sign_negative() != divisor.is_sign_negative()
{
return Ok(<O as Float>::nan());
}
match op {
ModRemOp::Mod => dividend - divisor * (dividend / divisor).floor(),
ModRemOp::Rem => dividend - divisor * (dividend / divisor).trunc(),
}
},
})
}
@ -539,6 +605,14 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
value.mul_by(scalar);
step.mul_by(scalar);
},
Self::ModRem {
ref mut dividend,
ref mut divisor,
..
} => {
dividend.mul_by(scalar);
divisor.mul_by(scalar);
},
}
}
@ -570,6 +644,14 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
value.visit_depth_first_internal(f);
step.visit_depth_first_internal(f);
},
Self::ModRem {
ref mut dividend,
ref mut divisor,
..
} => {
dividend.visit_depth_first_internal(f);
divisor.visit_depth_first_internal(f);
},
Self::Sum(ref mut children) | Self::MinMax(ref mut children, _) => {
for child in &mut **children {
child.visit_depth_first_internal(f);
@ -772,6 +854,36 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
},
};
},
Self::ModRem {
ref dividend,
ref divisor,
op,
} => {
let mut result = dividend.clone();
// In mod(A, B) only, if B is infinite and A has opposite sign to B
// (including an oppositely-signed zero), the result is NaN.
// https://drafts.csswg.org/css-values/#round-infinities
if matches!(op, ModRemOp::Mod) &&
divisor.is_infinite_leaf() &&
dividend.is_negative_leaf() != divisor.is_negative_leaf()
{
result.mul_by(f32::NAN);
return replace_self_with!(&mut *result);
}
let result = match op {
ModRemOp::Mod => dividend.try_op(divisor, |a, b| a - b * (a / b).floor()),
ModRemOp::Rem => dividend.try_op(divisor, |a, b| a - b * (a / b).trunc()),
};
let mut result = match result {
Ok(res) => res,
Err(..) => return,
};
return replace_self_with!(&mut result);
},
Self::MinMax(ref mut children, op) => {
let winning_order = match op {
MinMaxOp::Min => cmp::Ordering::Less,
@ -887,6 +999,14 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
true
},
Self::ModRem { op, .. } => {
dest.write_str(match op {
ModRemOp::Mod => "mod(",
ModRemOp::Rem => "rem(",
})?;
true
},
_ => {
if is_outermost {
dest.write_str("calc(")?;
@ -945,6 +1065,15 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
dest.write_str(", ")?;
step.to_css_impl(dest, false)?;
},
Self::ModRem {
ref dividend,
ref divisor,
..
} => {
dividend.to_css_impl(dest, false)?;
dest.write_str(", ")?;
divisor.to_css_impl(dest, false)?;
},
Self::Leaf(ref l) => l.to_css(dest)?,
}

View file

@ -8,7 +8,7 @@
use crate::parser::ParserContext;
use crate::values::generics::calc as generic;
use crate::values::generics::calc::{MinMaxOp, RoundingStrategy, SortKey};
use crate::values::generics::calc::{MinMaxOp, ModRemOp, RoundingStrategy, SortKey};
use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength};
use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength};
use crate::values::specified::{self, Angle, Time};
@ -54,6 +54,10 @@ pub enum MathFunction {
Clamp,
/// `round()`: https://drafts.csswg.org/css-values-4/#funcdef-round
Round,
/// `mod()`: https://drafts.csswg.org/css-values-4/#funcdef-mod
Mod,
/// `rem()`: https://drafts.csswg.org/css-values-4/#funcdef-rem
Rem,
/// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin
Sin,
/// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos
@ -490,6 +494,22 @@ impl CalcNode {
step: Box::new(step),
})
},
MathFunction::Mod | MathFunction::Rem => {
let dividend = Self::parse_argument(context, input, allowed_units)?;
input.expect_comma()?;
let divisor = Self::parse_argument(context, input, allowed_units)?;
let op = match function {
MathFunction::Mod => ModRemOp::Mod,
MathFunction::Rem => ModRemOp::Rem,
_ => unreachable!(),
};
Ok(Self::ModRem {
dividend: Box::new(dividend),
divisor: Box::new(divisor),
op,
})
},
MathFunction::Min | MathFunction::Max => {
// TODO(emilio): The common case for parse_comma_separated
// is just one element, but for min / max is two, really...
@ -780,6 +800,8 @@ impl CalcNode {
trig_enabled()
} else if matches!(function, Round) {
round_enabled()
} else if matches!(function, Mod | Rem) {
static_prefs::pref!("layout.css.mod-rem.enabled")
} else {
true
};