diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 29bed80d926..4222f2924ed 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -17,7 +17,7 @@ use crate::values::{specified, CSSFloat}; use crate::Zero; use app_units::Au; use std::fmt::{self, Write}; -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; use style_traits::{CSSPixel, CssWriter, ToCss}; pub use super::image::Image; @@ -402,6 +402,15 @@ impl Neg for CSSPixelLength { } } +impl Rem for CSSPixelLength { + type Output = Self; + + #[inline] + fn rem(self, other: Self) -> Self { + CSSPixelLength::new(self.0 % other.0) + } +} + impl Sub for CSSPixelLength { type Output = Self; diff --git a/components/style/values/computed/length_percentage.rs b/components/style/values/computed/length_percentage.rs index 21e0a4b0cfb..0afa1014d92 100644 --- a/components/style/values/computed/length_percentage.rs +++ b/components/style/values/computed/length_percentage.rs @@ -624,10 +624,10 @@ impl PartialOrd for CalcLengthPercentageLeaf { } impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { - fn is_negative(&self) -> bool { + fn unitless_value(&self) -> f32 { match *self { - Self::Length(ref l) => l.px() < 0., - Self::Percentage(ref p) => p.0 < 0., + Self::Length(ref l) => l.px(), + Self::Percentage(ref p) => p.0, } } @@ -657,6 +657,28 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { Ok(()) } + fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { + match (self, other) { + ( + &CalcLengthPercentageLeaf::Length(ref one), + &CalcLengthPercentageLeaf::Length(ref other), + ) => Ok(CalcLengthPercentageLeaf::Length(Length::new(op( + one.px(), + other.px(), + )))), + ( + &CalcLengthPercentageLeaf::Percentage(one), + &CalcLengthPercentageLeaf::Percentage(other), + ) => Ok(CalcLengthPercentageLeaf::Percentage(Percentage(op( + one.0, other.0, + )))), + _ => Err(()), + } + } + fn mul_by(&mut self, scalar: f32) { match *self { Self::Length(ref mut l) => *l = *l * scalar, diff --git a/components/style/values/computed/percentage.rs b/components/style/values/computed/percentage.rs index 4e9732ade2c..30ef5ab0f0d 100644 --- a/components/style/values/computed/percentage.rs +++ b/components/style/values/computed/percentage.rs @@ -77,6 +77,30 @@ impl std::ops::AddAssign for Percentage { } } +impl std::ops::Add for Percentage { + type Output = Self; + + fn add(self, other: Self) -> Self { + Percentage(self.0 + other.0) + } +} + +impl std::ops::Sub for Percentage { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Percentage(self.0 - other.0) + } +} + +impl std::ops::Rem for Percentage { + type Output = Self; + + fn rem(self, other: Self) -> Self { + Percentage(self.0 % other.0) + } +} + impl ToCss for Percentage { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where diff --git a/components/style/values/generics/calc.rs b/components/style/values/generics/calc.rs index 301ffb5cd62..91941a6b71b 100644 --- a/components/style/values/generics/calc.rs +++ b/components/style/values/generics/calc.rs @@ -6,10 +6,10 @@ //! //! [calc]: https://drafts.csswg.org/css-values/#calc-notation -use crate::Zero; +use num_traits::{Float, Zero}; use smallvec::SmallVec; use std::fmt::{self, Write}; -use std::ops::Add; +use std::ops::{Add, Div, Mul, Rem, Sub}; use std::{cmp, mem}; use style_traits::{CssWriter, ToCss}; @@ -34,6 +34,35 @@ pub enum MinMaxOp { Max, } +/// The strategy used in `round()` +#[derive( + Clone, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + ToAnimatedZero, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum RoundingStrategy { + /// `round(nearest, a, b)` + /// round a to the nearest multiple of b + Nearest, + /// `round(up, a, b)` + /// round a up to the nearest multiple of b + Up, + /// `round(down, a, b)` + /// round a down to the nearest multiple of b + Down, + /// `round(to-zero, a, b)` + /// round a to the nearest multiple of b that is towards zero + ToZero, +} + /// This determines the order in which we serialize members of a calc() sum. /// /// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children @@ -124,18 +153,52 @@ pub enum GenericCalcNode { /// The maximum value. max: Box>, }, + /// A `round()` function. + Round { + /// The rounding strategy. + strategy: RoundingStrategy, + /// The value to round. + value: Box>, + /// The step value. + step: Box>, + }, } pub use self::GenericCalcNode as CalcNode; /// A trait that represents all the stuff a valid leaf of a calc expression. pub trait CalcNodeLeaf: Clone + Sized + PartialOrd + PartialEq + ToCss { + /// Returns the unitless value of this leaf. + fn unitless_value(&self) -> f32; + /// Whether this value is known-negative. - fn is_negative(&self) -> bool; + fn is_negative(&self) -> bool { + self.unitless_value().is_sign_negative() + } + + /// Whether this value is infinite. + fn is_infinite(&self) -> bool { + self.unitless_value().is_infinite() + } + + /// Whether this value is zero. + fn is_zero(&self) -> bool { + self.unitless_value().is_zero() + } + + /// Whether this value is NaN. + fn is_nan(&self) -> bool { + self.unitless_value().is_nan() + } /// Tries to merge one sum to another, that is, perform `x` + `y`. fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()>; + /// Tries a generic arithmetic operation. + fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32; + /// Multiplies the leaf by a given scalar number. fn mul_by(&mut self, scalar: f32); @@ -182,6 +245,19 @@ impl CalcNode { } } + /// Tries to apply a generic arithmentic operator + fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { + match (self, other) { + (&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => { + Ok(CalcNode::Leaf(one.try_op(other, op)?)) + }, + _ => Err(()), + } + } + /// Convert this `CalcNode` into a `CalcNode` with a different leaf kind. pub fn map_leaves(&self, mut map: F) -> CalcNode where @@ -225,6 +301,19 @@ impl CalcNode { 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, + } + }, } } @@ -235,14 +324,30 @@ impl CalcNode { mut leaf_to_output_fn: impl FnMut(&L) -> Result, ) -> Result where - O: PartialOrd + PartialEq + Add + Zero, + O: PartialOrd + + PartialEq + + Add + + Mul + + Div + + Sub + + Zero + + Float + + Copy, { self.resolve_internal(&mut leaf_to_output_fn) } fn resolve_internal(&self, leaf_to_output_fn: &mut F) -> Result where - O: PartialOrd + PartialEq + Add + Zero, + O: PartialOrd + + PartialEq + + Add + + Mul + + Div + + Sub + + Zero + + Float + + Copy, F: FnMut(&L) -> Result, { Ok(match *self { @@ -286,6 +391,84 @@ impl CalcNode { } result }, + Self::Round { + strategy, + ref value, + ref step, + } => { + let value = value.resolve_internal(leaf_to_output_fn)?; + let step = step.resolve_internal(leaf_to_output_fn)?; + + // TODO(emilio): Seems like at least a few of these + // special-cases could be removed if we do the math in a + // particular order. + if step.is_zero() { + return Ok(::nan()); + } + + if value.is_infinite() && step.is_infinite() { + return Ok(::nan()); + } + + if value.is_infinite() { + return Ok(value); + } + + if step.is_infinite() { + match strategy { + RoundingStrategy::Nearest | RoundingStrategy::ToZero => { + return if value.is_sign_negative() { + Ok(::neg_zero()) + } else { + Ok(::zero()) + } + }, + RoundingStrategy::Up => { + return if !value.is_sign_negative() && !value.is_zero() { + Ok(::infinity()) + } else if !value.is_sign_negative() && value.is_zero() { + Ok(value) + } else { + Ok(::neg_zero()) + } + }, + RoundingStrategy::Down => { + return if value.is_sign_negative() && !value.is_zero() { + Ok(::neg_infinity()) + } else if value.is_sign_negative() && value.is_zero() { + Ok(value) + } else { + Ok(::zero()) + } + }, + } + } + + let div = value / step; + let lower_bound = div.floor() * step; + let upper_bound = div.ceil() * step; + + match strategy { + RoundingStrategy::Nearest => { + // In case of a tie, use the upper bound + if value - lower_bound < upper_bound - value { + lower_bound + } else { + upper_bound + } + }, + RoundingStrategy::Up => upper_bound, + RoundingStrategy::Down => lower_bound, + RoundingStrategy::ToZero => { + // In case of a tie, use the upper bound + if lower_bound.abs() < upper_bound.abs() { + lower_bound + } else { + upper_bound + } + }, + } + }, }) } @@ -296,6 +479,20 @@ impl CalcNode { } } + fn is_zero_leaf(&self) -> bool { + match *self { + Self::Leaf(ref l) => l.is_zero(), + _ => false, + } + } + + fn is_infinite_leaf(&self) -> bool { + match *self { + Self::Leaf(ref l) => l.is_infinite(), + _ => false, + } + } + /// Multiplies the node by a scalar. pub fn mul_by(&mut self, scalar: f32) { match *self { @@ -334,6 +531,14 @@ impl CalcNode { mem::swap(min, max); } }, + Self::Round { + ref mut value, + ref mut step, + .. + } => { + value.mul_by(scalar); + step.mul_by(scalar); + }, } } @@ -357,6 +562,14 @@ impl CalcNode { 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::Sum(ref mut children) | Self::MinMax(ref mut children, _) => { for child in &mut **children { child.visit_depth_first_internal(f); @@ -432,6 +645,133 @@ impl CalcNode { // Otherwise we're the center node. return replace_self_with!(&mut **center); }, + Self::Round { + strategy, + ref mut value, + ref mut step, + } => { + if step.is_zero_leaf() { + value.mul_by(f32::NAN); + return replace_self_with!(&mut **value); + } + + if value.is_infinite_leaf() && step.is_infinite_leaf() { + value.mul_by(f32::NAN); + return replace_self_with!(&mut **value); + } + + if value.is_infinite_leaf() { + return replace_self_with!(&mut **value); + } + + if step.is_infinite_leaf() { + match strategy { + RoundingStrategy::Nearest | RoundingStrategy::ToZero => { + value.mul_by(0.); + return replace_self_with!(&mut **value); + }, + RoundingStrategy::Up => { + if !value.is_negative_leaf() && !value.is_zero_leaf() { + value.mul_by(f32::INFINITY); + return replace_self_with!(&mut **value); + } else if !value.is_negative_leaf() && value.is_zero_leaf() { + return replace_self_with!(&mut **value); + } else { + value.mul_by(0.); + return replace_self_with!(&mut **value); + } + }, + RoundingStrategy::Down => { + if value.is_negative_leaf() && !value.is_zero_leaf() { + value.mul_by(f32::INFINITY); + return replace_self_with!(&mut **value); + } else if value.is_negative_leaf() && value.is_zero_leaf() { + return replace_self_with!(&mut **value); + } else { + value.mul_by(0.); + return replace_self_with!(&mut **value); + } + }, + } + } + + if step.is_negative_leaf() { + step.negate(); + } + + let remainder = match value.try_op(step, Rem::rem) { + Ok(res) => res, + Err(..) => return, + }; + + let (mut lower_bound, mut upper_bound) = if value.is_negative_leaf() { + let upper_bound = match value.try_op(&remainder, Sub::sub) { + Ok(res) => res, + Err(..) => return, + }; + + let lower_bound = match upper_bound.try_op(&step, Sub::sub) { + Ok(res) => res, + Err(..) => return, + }; + + (lower_bound, upper_bound) + } else { + let lower_bound = match value.try_op(&remainder, Sub::sub) { + Ok(res) => res, + Err(..) => return, + }; + + let upper_bound = match lower_bound.try_op(&step, Add::add) { + Ok(res) => res, + Err(..) => return, + }; + + (lower_bound, upper_bound) + }; + + match strategy { + RoundingStrategy::Nearest => { + let lower_diff = match value.try_op(&lower_bound, Sub::sub) { + Ok(res) => res, + Err(..) => return, + }; + + let upper_diff = match upper_bound.try_op(value, Sub::sub) { + Ok(res) => res, + Err(..) => return, + }; + + // In case of a tie, use the upper bound + if lower_diff < upper_diff { + return replace_self_with!(&mut lower_bound); + } else { + return replace_self_with!(&mut upper_bound); + } + }, + RoundingStrategy::Up => return replace_self_with!(&mut upper_bound), + RoundingStrategy::Down => return replace_self_with!(&mut lower_bound), + RoundingStrategy::ToZero => { + let mut lower_diff = lower_bound.clone(); + let mut upper_diff = upper_bound.clone(); + + if lower_diff.is_negative_leaf() { + lower_diff.negate(); + } + + if upper_diff.is_negative_leaf() { + upper_diff.negate(); + } + + // In case of a tie, use the upper bound + if lower_diff < upper_diff { + return replace_self_with!(&mut lower_bound); + } else { + return replace_self_with!(&mut upper_bound); + } + }, + }; + }, Self::MinMax(ref mut children, op) => { let winning_order = match op { MinMaxOp::Min => cmp::Ordering::Less, @@ -537,6 +877,16 @@ impl CalcNode { 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 + }, _ => { if is_outermost { dest.write_str("calc(")?; @@ -586,6 +936,15 @@ impl CalcNode { dest.write_str(", ")?; max.to_css_impl(dest, false)?; }, + Self::Round { + ref value, + ref step, + .. + } => { + value.to_css_impl(dest, false)?; + dest.write_str(", ")?; + step.to_css_impl(dest, false)?; + }, Self::Leaf(ref l) => l.to_css(dest)?, } diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index d27b408f3cc..75aefa78fb2 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -8,7 +8,7 @@ use crate::parser::ParserContext; use crate::values::generics::calc as generic; -use crate::values::generics::calc::{MinMaxOp, SortKey}; +use crate::values::generics::calc::{MinMaxOp, RoundingStrategy, SortKey}; use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength}; use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength}; use crate::values::specified::{self, Angle, Time}; @@ -45,6 +45,8 @@ pub enum MathFunction { Max, /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp Clamp, + /// `round()`: https://drafts.csswg.org/css-values-4/#funcdef-round + Round, /// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin Sin, /// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos @@ -185,12 +187,12 @@ impl PartialOrd for Leaf { } impl generic::CalcNodeLeaf for Leaf { - fn is_negative(&self) -> bool { + fn unitless_value(&self) -> f32 { match *self { - Self::Length(ref l) => l.is_negative(), - Self::Percentage(n) | Self::Number(n) => n < 0., - Self::Angle(ref a) => a.degrees() < 0., - Self::Time(ref t) => t.seconds() < 0., + Self::Length(ref l) => l.unitless_value(), + Self::Percentage(n) | Self::Number(n) => n, + Self::Angle(ref a) => a.degrees(), + Self::Time(ref t) => t.seconds(), } } @@ -300,7 +302,7 @@ impl generic::CalcNodeLeaf for Leaf { *one = specified::Time::from_calc(one.seconds() + other.seconds()); }, (&mut Length(ref mut one), &Length(ref other)) => { - *one = one.try_sum(other)?; + *one = one.try_op(other, std::ops::Add::add)?; }, _ => { match *other { @@ -314,6 +316,49 @@ impl generic::CalcNodeLeaf for Leaf { Ok(()) } + + fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { + use self::Leaf::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + + match (self, other) { + (&Number(one), &Number(other)) => { + return Ok(Leaf::Number(op(one, other))); + }, + (&Percentage(one), &Percentage(other)) => { + return Ok(Leaf::Percentage(op(one, other))); + }, + (&Angle(ref one), &Angle(ref other)) => { + return Ok(Leaf::Angle(specified::Angle::from_calc(op( + one.degrees(), + other.degrees(), + )))); + }, + (&Time(ref one), &Time(ref other)) => { + return Ok(Leaf::Time(specified::Time::from_calc(op( + one.seconds(), + other.seconds(), + )))); + }, + (&Length(ref one), &Length(ref other)) => { + return Ok(Leaf::Length(one.try_op(other, op)?)); + }, + _ => { + match *other { + Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) => {}, + } + unsafe { + debug_unreachable!(); + } + }, + } + } } /// A calc node representation for specified values. @@ -408,6 +453,36 @@ impl CalcNode { max: Box::new(max), }) }, + MathFunction::Round => { + let strategy = input.try_parse(parse_rounding_strategy); + + // = nearest | up | down | to-zero + // https://drafts.csswg.org/css-values-4/#calc-syntax + fn parse_rounding_strategy<'i, 't>( + input: &mut Parser<'i, 't>, + ) -> Result> { + Ok(try_match_ident_ignore_ascii_case! { input, + "nearest" => RoundingStrategy::Nearest, + "up" => RoundingStrategy::Up, + "down" => RoundingStrategy::Down, + "to-zero" => RoundingStrategy::ToZero, + }) + } + + if strategy.is_ok() { + input.expect_comma()?; + } + + let value = Self::parse_argument(context, input, allowed_units)?; + input.expect_comma()?; + let step = Self::parse_argument(context, input, allowed_units)?; + + Ok(Self::Round { + strategy: strategy.unwrap_or(RoundingStrategy::Nearest), + value: Box::new(value), + step: Box::new(step), + }) + }, MathFunction::Min | MathFunction::Max => { // TODO(emilio): The common case for parse_comma_separated // is just one element, but for min / max is two, really... @@ -694,7 +769,15 @@ impl CalcNode { }, }; - if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan | Atan2) && !trig_enabled() { + let enabled = if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan | Atan2) { + trig_enabled() + } else if matches!(function, Round) { + static_prefs::pref!("layout.css.round.enabled") + } else { + true + }; + + if !enabled { return Err(location.new_unexpected_token_error(Token::Function(name.clone()))); } diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 7ae816aebd2..cc436a91c41 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -22,7 +22,7 @@ use crate::{Zero, ZeroNoPercent}; use app_units::Au; use cssparser::{Parser, Token}; use std::cmp; -use std::ops::{Add, Mul}; +use std::ops::{Add, Mul, Rem, Sub}; use style_traits::values::specified::AllowedNumericType; use style_traits::{ParseError, SpecifiedValueInfo, StyleParseErrorKind}; @@ -88,11 +88,6 @@ impl FontBaseSize { } impl FontRelativeLength { - /// Return true if this is a zero value. - fn is_zero(&self) -> bool { - self.unitless_value() == 0. - } - /// Return the unitless, raw value. fn unitless_value(&self) -> CSSFloat { match *self { @@ -105,11 +100,10 @@ impl FontRelativeLength { } } - fn is_negative(&self) -> bool { - self.unitless_value() < 0. - } - - fn try_sum(&self, other: &Self) -> Result { + fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { use self::FontRelativeLength::*; if std::mem::discriminant(self) != std::mem::discriminant(other) { @@ -117,19 +111,19 @@ impl FontRelativeLength { } Ok(match (self, other) { - (&Em(one), &Em(other)) => Em(one + other), - (&Ex(one), &Ex(other)) => Ex(one + other), - (&Ch(one), &Ch(other)) => Ch(one + other), - (&Cap(one), &Cap(other)) => Cap(one + other), - (&Ic(one), &Ic(other)) => Ic(one + other), - (&Rem(one), &Rem(other)) => Rem(one + other), + (&Em(one), &Em(other)) => Em(op(one, other)), + (&Ex(one), &Ex(other)) => Ex(op(one, other)), + (&Ch(one), &Ch(other)) => Ch(op(one, other)), + (&Cap(one), &Cap(other)) => Cap(op(one, other)), + (&Ic(one), &Ic(other)) => Ic(op(one, other)), + (&Rem(one), &Rem(other)) => Rem(op(one, other)), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {}, } - debug_unreachable!("Forgot to handle unit in try_sum()") + debug_unreachable!("Forgot to handle unit in try_op()") }, }) } @@ -384,15 +378,6 @@ pub enum ViewportPercentageLength { } impl ViewportPercentageLength { - /// Return true if this is a zero value. - fn is_zero(&self) -> bool { - self.unitless_value() == 0. - } - - fn is_negative(&self) -> bool { - self.unitless_value() < 0. - } - /// Return the unitless, raw value. fn unitless_value(&self) -> CSSFloat { self.unpack().2 @@ -430,7 +415,11 @@ impl ViewportPercentageLength { ViewportPercentageLength::Dvi(v) => (ViewportVariant::Dynamic, ViewportUnit::Vi, v), } } - fn try_sum(&self, other: &Self) -> Result { + + fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { use self::ViewportPercentageLength::*; if std::mem::discriminant(self) != std::mem::discriminant(other) { @@ -438,30 +427,30 @@ impl ViewportPercentageLength { } Ok(match (self, other) { - (&Vw(one), &Vw(other)) => Vw(one + other), - (&Svw(one), &Svw(other)) => Svw(one + other), - (&Lvw(one), &Lvw(other)) => Lvw(one + other), - (&Dvw(one), &Dvw(other)) => Dvw(one + other), - (&Vh(one), &Vh(other)) => Vh(one + other), - (&Svh(one), &Svh(other)) => Svh(one + other), - (&Lvh(one), &Lvh(other)) => Lvh(one + other), - (&Dvh(one), &Dvh(other)) => Dvh(one + other), - (&Vmin(one), &Vmin(other)) => Vmin(one + other), - (&Svmin(one), &Svmin(other)) => Svmin(one + other), - (&Lvmin(one), &Lvmin(other)) => Lvmin(one + other), - (&Dvmin(one), &Dvmin(other)) => Dvmin(one + other), - (&Vmax(one), &Vmax(other)) => Vmax(one + other), - (&Svmax(one), &Svmax(other)) => Svmax(one + other), - (&Lvmax(one), &Lvmax(other)) => Lvmax(one + other), - (&Dvmax(one), &Dvmax(other)) => Dvmax(one + other), - (&Vb(one), &Vb(other)) => Vb(one + other), - (&Svb(one), &Svb(other)) => Svb(one + other), - (&Lvb(one), &Lvb(other)) => Lvb(one + other), - (&Dvb(one), &Dvb(other)) => Dvb(one + other), - (&Vi(one), &Vi(other)) => Vi(one + other), - (&Svi(one), &Svi(other)) => Svi(one + other), - (&Lvi(one), &Lvi(other)) => Lvi(one + other), - (&Dvi(one), &Dvi(other)) => Dvi(one + other), + (&Vw(one), &Vw(other)) => Vw(op(one, other)), + (&Svw(one), &Svw(other)) => Svw(op(one, other)), + (&Lvw(one), &Lvw(other)) => Lvw(op(one, other)), + (&Dvw(one), &Dvw(other)) => Dvw(op(one, other)), + (&Vh(one), &Vh(other)) => Vh(op(one, other)), + (&Svh(one), &Svh(other)) => Svh(op(one, other)), + (&Lvh(one), &Lvh(other)) => Lvh(op(one, other)), + (&Dvh(one), &Dvh(other)) => Dvh(op(one, other)), + (&Vmin(one), &Vmin(other)) => Vmin(op(one, other)), + (&Svmin(one), &Svmin(other)) => Svmin(op(one, other)), + (&Lvmin(one), &Lvmin(other)) => Lvmin(op(one, other)), + (&Dvmin(one), &Dvmin(other)) => Dvmin(op(one, other)), + (&Vmax(one), &Vmax(other)) => Vmax(op(one, other)), + (&Svmax(one), &Svmax(other)) => Svmax(op(one, other)), + (&Lvmax(one), &Lvmax(other)) => Lvmax(op(one, other)), + (&Dvmax(one), &Dvmax(other)) => Dvmax(op(one, other)), + (&Vb(one), &Vb(other)) => Vb(op(one, other)), + (&Svb(one), &Svb(other)) => Svb(op(one, other)), + (&Lvb(one), &Lvb(other)) => Lvb(op(one, other)), + (&Dvb(one), &Dvb(other)) => Dvb(op(one, other)), + (&Vi(one), &Vi(other)) => Vi(op(one, other)), + (&Svi(one), &Svi(other)) => Svi(op(one, other)), + (&Lvi(one), &Lvi(other)) => Lvi(op(one, other)), + (&Dvi(one), &Dvi(other)) => Dvi(op(one, other)), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { @@ -471,7 +460,7 @@ impl ViewportPercentageLength { Svmax(..) | Lvmax(..) | Dvmax(..) | Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, } - debug_unreachable!("Forgot to handle unit in try_sum()") + debug_unreachable!("Forgot to handle unit in try_op()") }, }) } @@ -563,14 +552,6 @@ impl AbsoluteLength { } } - fn is_zero(&self) -> bool { - self.unitless_value() == 0. - } - - fn is_negative(&self) -> bool { - self.unitless_value() < 0. - } - /// Convert this into a pixel value. #[inline] pub fn to_px(&self) -> CSSFloat { @@ -587,6 +568,22 @@ impl AbsoluteLength { }; pixel.min(f32::MAX).max(f32::MIN) } + + fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { + Ok(match (self, other) { + (AbsoluteLength::Px(x), AbsoluteLength::Px(y)) => AbsoluteLength::Px(op(*x, *y)), + (AbsoluteLength::In(x), AbsoluteLength::In(y)) => AbsoluteLength::In(op(*x, *y)), + (AbsoluteLength::Cm(x), AbsoluteLength::Cm(y)) => AbsoluteLength::Cm(op(*x, *y)), + (AbsoluteLength::Mm(x), AbsoluteLength::Mm(y)) => AbsoluteLength::Mm(op(*x, *y)), + (AbsoluteLength::Q(x), AbsoluteLength::Q(y)) => AbsoluteLength::Q(op(*x, *y)), + (AbsoluteLength::Pt(x), AbsoluteLength::Pt(y)) => AbsoluteLength::Pt(op(*x, *y)), + (AbsoluteLength::Pc(x), AbsoluteLength::Pc(y)) => AbsoluteLength::Pc(op(*x, *y)), + _ => AbsoluteLength::Px(op(self.to_px(), other.to_px())), + }) + } } impl ToComputedValue for AbsoluteLength { @@ -679,15 +676,10 @@ impl ContainerRelativeLength { } } - fn is_zero(&self) -> bool { - self.unitless_value() == 0. - } - - fn is_negative(&self) -> bool { - self.unitless_value() < 0. - } - - fn try_sum(&self, other: &Self) -> Result { + pub(crate) fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { use self::ContainerRelativeLength::*; if std::mem::discriminant(self) != std::mem::discriminant(other) { @@ -695,12 +687,12 @@ impl ContainerRelativeLength { } Ok(match (self, other) { - (&Cqw(one), &Cqw(other)) => Cqw(one + other), - (&Cqh(one), &Cqh(other)) => Cqh(one + other), - (&Cqi(one), &Cqi(other)) => Cqi(one + other), - (&Cqb(one), &Cqb(other)) => Cqb(one + other), - (&Cqmin(one), &Cqmin(other)) => Cqmin(one + other), - (&Cqmax(one), &Cqmax(other)) => Cqmax(one + other), + (&Cqw(one), &Cqw(other)) => Cqw(op(one, other)), + (&Cqh(one), &Cqh(other)) => Cqh(op(one, other)), + (&Cqi(one), &Cqi(other)) => Cqi(op(one, other)), + (&Cqb(one), &Cqb(other)) => Cqb(op(one, other)), + (&Cqmin(one), &Cqmin(other)) => Cqmin(op(one, other)), + (&Cqmax(one), &Cqmax(other)) => Cqmax(op(one, other)), // See https://github.com/rust-lang/rust/issues/68867, then // https://github.com/rust-lang/rust/pull/95161. rustc isn't @@ -709,7 +701,7 @@ impl ContainerRelativeLength { match *self { Cqw(..) | Cqh(..) | Cqi(..) | Cqb(..) | Cqmin(..) | Cqmax(..) => {}, } - debug_unreachable!("Forgot to handle unit in try_sum()") + debug_unreachable!("Forgot to handle unit in try_op()") }, }) } @@ -750,6 +742,42 @@ fn are_container_queries_enabled() -> bool { false } +impl Sub for AbsoluteLength { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self { + match (self, rhs) { + (AbsoluteLength::Px(x), AbsoluteLength::Px(y)) => AbsoluteLength::Px(x - y), + (AbsoluteLength::In(x), AbsoluteLength::In(y)) => AbsoluteLength::In(x - y), + (AbsoluteLength::Cm(x), AbsoluteLength::Cm(y)) => AbsoluteLength::Cm(x - y), + (AbsoluteLength::Mm(x), AbsoluteLength::Mm(y)) => AbsoluteLength::Mm(x - y), + (AbsoluteLength::Q(x), AbsoluteLength::Q(y)) => AbsoluteLength::Q(x - y), + (AbsoluteLength::Pt(x), AbsoluteLength::Pt(y)) => AbsoluteLength::Pt(x - y), + (AbsoluteLength::Pc(x), AbsoluteLength::Pc(y)) => AbsoluteLength::Pc(x - y), + _ => AbsoluteLength::Px(self.to_px() - rhs.to_px()), + } + } +} + +impl Rem for AbsoluteLength { + type Output = Self; + + #[inline] + fn rem(self, rhs: Self) -> Self { + match (self, rhs) { + (AbsoluteLength::Px(x), AbsoluteLength::Px(y)) => AbsoluteLength::Px(x % y), + (AbsoluteLength::In(x), AbsoluteLength::In(y)) => AbsoluteLength::In(x % y), + (AbsoluteLength::Cm(x), AbsoluteLength::Cm(y)) => AbsoluteLength::Cm(x % y), + (AbsoluteLength::Mm(x), AbsoluteLength::Mm(y)) => AbsoluteLength::Mm(x % y), + (AbsoluteLength::Q(x), AbsoluteLength::Q(y)) => AbsoluteLength::Q(x % y), + (AbsoluteLength::Pt(x), AbsoluteLength::Pt(y)) => AbsoluteLength::Pt(x % y), + (AbsoluteLength::Pc(x), AbsoluteLength::Pc(y)) => AbsoluteLength::Pc(x % y), + _ => AbsoluteLength::Px(self.to_px() % rhs.to_px()), + } + } +} + /// A `` without taking `calc` expressions into account /// /// @@ -811,13 +839,22 @@ impl NoCalcLength { /// Returns whether the value of this length without unit is less than zero. pub fn is_negative(&self) -> bool { - match *self { - NoCalcLength::Absolute(v) => v.is_negative(), - NoCalcLength::FontRelative(v) => v.is_negative(), - NoCalcLength::ViewportPercentage(v) => v.is_negative(), - NoCalcLength::ContainerRelative(v) => v.is_negative(), - NoCalcLength::ServoCharacterWidth(c) => c.0 < 0, - } + self.unitless_value().is_sign_negative() + } + + /// Returns whether the value of this length without unit is equal to zero. + pub fn is_zero(&self) -> bool { + self.unitless_value() == 0.0 + } + + /// Returns whether the value of this length without unit is infinite. + pub fn is_infinite(&self) -> bool { + self.unitless_value().is_infinite() + } + + /// Returns whether the value of this length without unit is NaN. + pub fn is_nan(&self) -> bool { + self.unitless_value().is_nan() } /// Whether text-only zoom should be applied to this length. @@ -952,8 +989,10 @@ impl NoCalcLength { }) } - /// Try to sume two lengths if compatible into the left hand side. - pub(crate) fn try_sum(&self, other: &Self) -> Result { + pub(crate) fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { use self::NoCalcLength::*; if std::mem::discriminant(self) != std::mem::discriminant(other) { @@ -961,16 +1000,18 @@ impl NoCalcLength { } Ok(match (self, other) { - (&Absolute(ref one), &Absolute(ref other)) => Absolute(*one + *other), - (&FontRelative(ref one), &FontRelative(ref other)) => FontRelative(one.try_sum(other)?), + (&Absolute(ref one), &Absolute(ref other)) => Absolute(one.try_op(other, op)?), + (&FontRelative(ref one), &FontRelative(ref other)) => { + FontRelative(one.try_op(other, op)?) + }, (&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => { - ViewportPercentage(one.try_sum(other)?) + ViewportPercentage(one.try_op(other, op)?) }, (&ContainerRelative(ref one), &ContainerRelative(ref other)) => { - ContainerRelative(one.try_sum(other)?) + ContainerRelative(one.try_op(other, op)?) }, (&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => { - ServoCharacterWidth(CharacterWidth(one.0 + other.0)) + ServoCharacterWidth(CharacterWidth(op(one.0 as f32, other.0 as f32) as i32)) }, // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. @@ -982,7 +1023,7 @@ impl NoCalcLength { ContainerRelative(..) | ServoCharacterWidth(..) => {}, } - debug_unreachable!("Forgot to handle unit in try_sum()") + debug_unreachable!("Forgot to handle unit in try_op()") }, }) } @@ -1045,13 +1086,7 @@ impl Zero for NoCalcLength { } fn is_zero(&self) -> bool { - match *self { - NoCalcLength::Absolute(v) => v.is_zero(), - NoCalcLength::FontRelative(v) => v.is_zero(), - NoCalcLength::ViewportPercentage(v) => v.is_zero(), - NoCalcLength::ContainerRelative(v) => v.is_zero(), - NoCalcLength::ServoCharacterWidth(v) => v.0 == 0, - } + NoCalcLength::is_zero(self) } } @@ -1170,7 +1205,7 @@ impl PartialOrd for ContainerRelativeLength { match *self { Cqw(..) | Cqh(..) | Cqi(..) | Cqb(..) | Cqmin(..) | Cqmax(..) => {}, } - debug_unreachable!("Forgot to handle unit in try_sum()") + debug_unreachable!("Forgot to handle unit in partial_cmp()") }, } }