style: Implement atan2(), and enable calc() trigonometric functions by default on nightly

We now have test coverage, so let's do this.

The remaining failures are just about infinity/nan, which is a
completely different feature.

Differential Revision: https://phabricator.services.mozilla.com/D154831
This commit is contained in:
Emilio Cobos Álvarez 2022-08-18 08:49:30 +00:00 committed by Martin Robinson
parent 03e84754cc
commit dd849de9d9
3 changed files with 180 additions and 117 deletions

View file

@ -158,6 +158,14 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
} }
} }
/// Returns the leaf if we can (if simplification has allowed it).
pub fn as_leaf(&self) -> Option<&L> {
match *self {
Self::Leaf(ref l) => Some(l),
_ => None,
}
}
/// Tries to merge one sum to another, that is, perform `x` + `y`. /// Tries to merge one sum to another, that is, perform `x` + `y`.
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
match (self, other) { match (self, other) {

View file

@ -43,6 +43,8 @@ pub enum MathFunction {
Acos, Acos,
/// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan /// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan
Atan, Atan,
/// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2
Atan2,
} }
/// A leaf node inside a `Calc` expression's AST. /// A leaf node inside a `Calc` expression's AST.
@ -60,6 +62,15 @@ pub enum Leaf {
Number(CSSFloat), Number(CSSFloat),
} }
impl Leaf {
fn as_length(&self) -> Option<&NoCalcLength> {
match *self {
Self::Length(ref l) => Some(l),
_ => None,
}
}
}
impl ToCss for Leaf { impl ToCss for Leaf {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where where
@ -75,23 +86,22 @@ impl ToCss for Leaf {
} }
} }
/// An expected unit we intend to parse within a `calc()` expression. bitflags! {
/// /// Expected units we allow parsing within a `calc()` expression.
/// This is used as a hint for the parser to fast-reject invalid expressions. ///
#[derive(Clone, Copy, PartialEq)] /// This is used as a hint for the parser to fast-reject invalid
enum CalcUnit { /// expressions. Numbers are always allowed because they multiply other
/// `<number>` /// units.
Number, struct CalcUnits: u8 {
/// `<length>` const LENGTH = 1 << 0;
Length, const PERCENTAGE = 1 << 1;
/// `<percentage>` const ANGLE = 1 << 2;
Percentage, const TIME = 1 << 3;
/// `<length> | <percentage>`
LengthPercentage, const LENGTH_PERCENTAGE = Self::LENGTH.bits | Self::PERCENTAGE.bits;
/// `<angle>` // NOTE: When you add to this, make sure to make Atan2 deal with these.
Angle, const ALL = Self::LENGTH.bits | Self::PERCENTAGE.bits | Self::ANGLE.bits | Self::TIME.bits;
/// `<time>` }
Time,
} }
/// A struct to hold a simplified `<length>` or `<percentage>` expression. /// A struct to hold a simplified `<length>` or `<percentage>` expression.
@ -108,6 +118,27 @@ pub struct CalcLengthPercentage {
pub node: CalcNode, pub node: CalcNode,
} }
impl CalcLengthPercentage {
fn same_unit_length_as(a: &Self, b: &Self) -> Option<(CSSFloat, CSSFloat)> {
use generic::CalcNodeLeaf;
debug_assert_eq!(a.clamping_mode, b.clamping_mode);
debug_assert_eq!(a.clamping_mode, AllowedNumericType::All);
let a = a.node.as_leaf()?;
let b = b.node.as_leaf()?;
if a.sort_key() != b.sort_key() {
return None;
}
let a = a.as_length()?.unitless_value();
let b = b.as_length()?.unitless_value();
return Some((a, b))
}
}
impl SpecifiedValueInfo for CalcLengthPercentage {} impl SpecifiedValueInfo for CalcLengthPercentage {}
impl PartialOrd for Leaf { impl PartialOrd for Leaf {
@ -277,71 +308,47 @@ pub type CalcNode = generic::GenericCalcNode<Leaf>;
impl CalcNode { impl CalcNode {
/// Tries to parse a single element in the expression, that is, a /// Tries to parse a single element in the expression, that is, a
/// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to /// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to
/// `expected_unit`. /// `allowed_units`.
/// ///
/// May return a "complex" `CalcNode`, in the presence of a parenthesized /// May return a "complex" `CalcNode`, in the presence of a parenthesized
/// expression, for example. /// expression, for example.
fn parse_one<'i, 't>( fn parse_one<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
expected_unit: CalcUnit, allowed_units: CalcUnits,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location(); let location = input.current_source_location();
match (input.next()?, expected_unit) { match input.next()? {
(&Token::Number { value, .. }, _) => Ok(CalcNode::Leaf(Leaf::Number(value))), &Token::Number { value, .. } => Ok(CalcNode::Leaf(Leaf::Number(value))),
( &Token::Dimension { value, ref unit, .. } => {
&Token::Dimension { if allowed_units.intersects(CalcUnits::LENGTH) {
value, ref unit, .. if let Ok(l) = NoCalcLength::parse_dimension(context, value, unit) {
}, return Ok(CalcNode::Leaf(Leaf::Length(l)));
CalcUnit::Length, }
) |
(
&Token::Dimension {
value, ref unit, ..
},
CalcUnit::LengthPercentage,
) => match NoCalcLength::parse_dimension(context, value, unit) {
Ok(l) => Ok(CalcNode::Leaf(Leaf::Length(l))),
Err(()) => Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
},
(
&Token::Dimension {
value, ref unit, ..
},
CalcUnit::Angle,
) => {
match Angle::parse_dimension(value, unit, /* from_calc = */ true) {
Ok(a) => Ok(CalcNode::Leaf(Leaf::Angle(a))),
Err(()) => {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
},
} }
}, if allowed_units.intersects(CalcUnits::ANGLE) {
( if let Ok(a) = Angle::parse_dimension(value, unit, /* from_calc = */ true) {
&Token::Dimension { return Ok(CalcNode::Leaf(Leaf::Angle(a)));
value, ref unit, .. }
},
CalcUnit::Time,
) => {
match Time::parse_dimension(value, unit, /* from_calc = */ true) {
Ok(t) => Ok(CalcNode::Leaf(Leaf::Time(t))),
Err(()) => {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
},
} }
if allowed_units.intersects(CalcUnits::TIME) {
if let Ok(t) = Time::parse_dimension(value, unit, /* from_calc = */ true) {
return Ok(CalcNode::Leaf(Leaf::Time(t)));
}
}
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}, },
(&Token::Percentage { unit_value, .. }, CalcUnit::LengthPercentage) | &Token::Percentage { unit_value, .. } if allowed_units.intersects(CalcUnits::PERCENTAGE) => {
(&Token::Percentage { unit_value, .. }, CalcUnit::Percentage) => {
Ok(CalcNode::Leaf(Leaf::Percentage(unit_value))) Ok(CalcNode::Leaf(Leaf::Percentage(unit_value)))
}, }
(&Token::ParenthesisBlock, _) => input.parse_nested_block(|input| { &Token::ParenthesisBlock => input.parse_nested_block(|input| {
CalcNode::parse_argument(context, input, expected_unit) CalcNode::parse_argument(context, input, allowed_units)
}), }),
(&Token::Function(ref name), _) => { &Token::Function(ref name) => {
let function = CalcNode::math_function(name, location)?; let function = CalcNode::math_function(name, location)?;
CalcNode::parse(context, input, function, expected_unit) CalcNode::parse(context, input, function, allowed_units)
}, },
(&Token::Ident(ref ident), _) => { &Token::Ident(ref ident) => {
if !trig_enabled() { if !trig_enabled() {
return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))); return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));
} }
@ -352,7 +359,7 @@ impl CalcNode {
}; };
Ok(CalcNode::Leaf(Leaf::Number(number))) Ok(CalcNode::Leaf(Leaf::Number(number)))
}, },
(t, _) => Err(location.new_unexpected_token_error(t.clone())), t => Err(location.new_unexpected_token_error(t.clone())),
} }
} }
@ -363,20 +370,20 @@ impl CalcNode {
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
function: MathFunction, function: MathFunction,
expected_unit: CalcUnit, allowed_units: CalcUnits,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
// TODO: Do something different based on the function name. In // TODO: Do something different based on the function name. In
// particular, for non-calc function we need to take a list of // particular, for non-calc function we need to take a list of
// comma-separated arguments and such. // comma-separated arguments and such.
input.parse_nested_block(|input| { input.parse_nested_block(|input| {
match function { match function {
MathFunction::Calc => Self::parse_argument(context, input, expected_unit), MathFunction::Calc => Self::parse_argument(context, input, allowed_units),
MathFunction::Clamp => { MathFunction::Clamp => {
let min = Self::parse_argument(context, input, expected_unit)?; let min = Self::parse_argument(context, input, allowed_units)?;
input.expect_comma()?; input.expect_comma()?;
let center = Self::parse_argument(context, input, expected_unit)?; let center = Self::parse_argument(context, input, allowed_units)?;
input.expect_comma()?; input.expect_comma()?;
let max = Self::parse_argument(context, input, expected_unit)?; let max = Self::parse_argument(context, input, allowed_units)?;
Ok(Self::Clamp { Ok(Self::Clamp {
min: Box::new(min), min: Box::new(min),
center: Box::new(center), center: Box::new(center),
@ -390,7 +397,7 @@ impl CalcNode {
// Consider adding an API to cssparser to specify the // Consider adding an API to cssparser to specify the
// initial vector capacity? // initial vector capacity?
let arguments = input.parse_comma_separated(|input| { let arguments = input.parse_comma_separated(|input| {
Self::parse_argument(context, input, expected_unit) Self::parse_argument(context, input, allowed_units)
})?; })?;
let op = match function { let op = match function {
@ -402,7 +409,7 @@ impl CalcNode {
Ok(Self::MinMax(arguments.into(), op)) Ok(Self::MinMax(arguments.into(), op))
}, },
MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => { MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => {
let argument = Self::parse_argument(context, input, CalcUnit::Angle)?; let argument = Self::parse_argument(context, input, CalcUnits::ANGLE)?;
let radians = match argument.to_number() { let radians = match argument.to_number() {
Ok(v) => v, Ok(v) => v,
Err(()) => match argument.to_angle() { Err(()) => match argument.to_angle() {
@ -425,7 +432,7 @@ impl CalcNode {
Ok(Self::Leaf(Leaf::Number(number))) Ok(Self::Leaf(Leaf::Number(number)))
}, },
MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => { MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => {
let argument = Self::parse_argument(context, input, CalcUnit::Number)?; let argument = Self::parse_argument(context, input, CalcUnits::empty())?;
let number = match argument.to_number() { let number = match argument.to_number() {
Ok(v) => v, Ok(v) => v,
Err(()) => { Err(()) => {
@ -444,6 +451,46 @@ impl CalcNode {
}, },
}; };
Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians))))
},
MathFunction::Atan2 => {
let a = Self::parse_argument(context, input, CalcUnits::ALL)?;
input.expect_comma()?;
let b = Self::parse_argument(context, input, CalcUnits::ALL)?;
fn resolve_atan2(a: CalcNode, b: CalcNode) -> Result<CSSFloat, ()> {
if let Ok(a) = a.to_number() {
let b = b.to_number()?;
return Ok(a.atan2(b));
}
if let Ok(a) = a.to_percentage() {
let b = b.to_percentage()?;
return Ok(a.atan2(b));
}
if let Ok(a) = a.to_time() {
let b = b.to_time()?;
return Ok(a.seconds().atan2(b.seconds()));
}
if let Ok(a) = a.to_angle() {
let b = b.to_angle()?;
return Ok(a.radians().atan2(b.radians()));
}
let a = a.into_length_or_percentage(AllowedNumericType::All)?;
let b = b.into_length_or_percentage(AllowedNumericType::All)?;
let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?;
return Ok(a.atan2(b));
}
let radians = match resolve_atan2(a, b) {
Ok(v) => v,
Err(()) => return Err(
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
),
};
Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians))))
}, },
} }
@ -453,10 +500,10 @@ impl CalcNode {
fn parse_argument<'i, 't>( fn parse_argument<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
expected_unit: CalcUnit, allowed_units: CalcUnits,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
let mut sum = SmallVec::<[CalcNode; 1]>::new(); let mut sum = SmallVec::<[CalcNode; 1]>::new();
sum.push(Self::parse_product(context, input, expected_unit)?); sum.push(Self::parse_product(context, input, allowed_units)?);
loop { loop {
let start = input.state(); let start = input.state();
@ -467,10 +514,10 @@ impl CalcNode {
} }
match *input.next()? { match *input.next()? {
Token::Delim('+') => { Token::Delim('+') => {
sum.push(Self::parse_product(context, input, expected_unit)?); sum.push(Self::parse_product(context, input, allowed_units)?);
}, },
Token::Delim('-') => { Token::Delim('-') => {
let mut rhs = Self::parse_product(context, input, expected_unit)?; let mut rhs = Self::parse_product(context, input, allowed_units)?;
rhs.negate(); rhs.negate();
sum.push(rhs); sum.push(rhs);
}, },
@ -506,15 +553,15 @@ impl CalcNode {
fn parse_product<'i, 't>( fn parse_product<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
expected_unit: CalcUnit, allowed_units: CalcUnits,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
let mut node = Self::parse_one(context, input, expected_unit)?; let mut node = Self::parse_one(context, input, allowed_units)?;
loop { loop {
let start = input.state(); let start = input.state();
match input.next() { match input.next() {
Ok(&Token::Delim('*')) => { Ok(&Token::Delim('*')) => {
let rhs = Self::parse_one(context, input, expected_unit)?; let rhs = Self::parse_one(context, input, allowed_units)?;
if let Ok(rhs) = rhs.to_number() { if let Ok(rhs) = rhs.to_number() {
node.mul_by(rhs); node.mul_by(rhs);
} else if let Ok(number) = node.to_number() { } else if let Ok(number) = node.to_number() {
@ -527,7 +574,7 @@ impl CalcNode {
} }
}, },
Ok(&Token::Delim('/')) => { Ok(&Token::Delim('/')) => {
let rhs = Self::parse_one(context, input, expected_unit)?; let rhs = Self::parse_one(context, input, allowed_units)?;
// Dividing by units is not ok. // Dividing by units is not ok.
// //
// TODO(emilio): Eventually it should be. // TODO(emilio): Eventually it should be.
@ -627,7 +674,7 @@ impl CalcNode {
}, },
}; };
if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !trig_enabled() { if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan | Atan2) && !trig_enabled() {
return Err(location.new_unexpected_token_error(Token::Function(name.clone()))); return Err(location.new_unexpected_token_error(Token::Function(name.clone())));
} }
@ -650,7 +697,7 @@ impl CalcNode {
clamping_mode: AllowedNumericType, clamping_mode: AllowedNumericType,
function: MathFunction, function: MathFunction,
) -> Result<CalcLengthPercentage, ParseError<'i>> { ) -> Result<CalcLengthPercentage, ParseError<'i>> {
Self::parse(context, input, function, CalcUnit::LengthPercentage)? Self::parse(context, input, function, CalcUnits::LENGTH_PERCENTAGE)?
.into_length_or_percentage(clamping_mode) .into_length_or_percentage(clamping_mode)
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} }
@ -661,7 +708,7 @@ impl CalcNode {
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
function: MathFunction, function: MathFunction,
) -> Result<CSSFloat, ParseError<'i>> { ) -> Result<CSSFloat, ParseError<'i>> {
Self::parse(context, input, function, CalcUnit::Percentage)? Self::parse(context, input, function, CalcUnits::PERCENTAGE)?
.to_percentage() .to_percentage()
.map(crate::values::normalize) .map(crate::values::normalize)
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
@ -674,7 +721,7 @@ impl CalcNode {
clamping_mode: AllowedNumericType, clamping_mode: AllowedNumericType,
function: MathFunction, function: MathFunction,
) -> Result<CalcLengthPercentage, ParseError<'i>> { ) -> Result<CalcLengthPercentage, ParseError<'i>> {
Self::parse(context, input, function, CalcUnit::Length)? Self::parse(context, input, function, CalcUnits::LENGTH)?
.into_length_or_percentage(clamping_mode) .into_length_or_percentage(clamping_mode)
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} }
@ -685,7 +732,7 @@ impl CalcNode {
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
function: MathFunction, function: MathFunction,
) -> Result<CSSFloat, ParseError<'i>> { ) -> Result<CSSFloat, ParseError<'i>> {
Self::parse(context, input, function, CalcUnit::Number)? Self::parse(context, input, function, CalcUnits::empty())?
.to_number() .to_number()
.map(crate::values::normalize) .map(crate::values::normalize)
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
@ -697,7 +744,7 @@ impl CalcNode {
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
function: MathFunction, function: MathFunction,
) -> Result<Angle, ParseError<'i>> { ) -> Result<Angle, ParseError<'i>> {
Self::parse(context, input, function, CalcUnit::Angle)? Self::parse(context, input, function, CalcUnits::ANGLE)?
.to_angle() .to_angle()
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} }
@ -708,7 +755,7 @@ impl CalcNode {
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
function: MathFunction, function: MathFunction,
) -> Result<Time, ParseError<'i>> { ) -> Result<Time, ParseError<'i>> {
Self::parse(context, input, function, CalcUnit::Time)? Self::parse(context, input, function, CalcUnits::TIME)?
.to_time() .to_time()
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} }
@ -719,7 +766,7 @@ impl CalcNode {
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
function: MathFunction, function: MathFunction,
) -> Result<NumberOrPercentage, ParseError<'i>> { ) -> Result<NumberOrPercentage, ParseError<'i>> {
let node = Self::parse(context, input, function, CalcUnit::Percentage)?; let node = Self::parse(context, input, function, CalcUnits::PERCENTAGE)?;
if let Ok(value) = node.to_number() { if let Ok(value) = node.to_number() {
return Ok(NumberOrPercentage::Number { value }); return Ok(NumberOrPercentage::Number { value });
@ -737,7 +784,7 @@ impl CalcNode {
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
function: MathFunction, function: MathFunction,
) -> Result<AngleOrNumber, ParseError<'i>> { ) -> Result<AngleOrNumber, ParseError<'i>> {
let node = Self::parse(context, input, function, CalcUnit::Angle)?; let node = Self::parse(context, input, function, CalcUnits::ANGLE)?;
if let Ok(angle) = node.to_angle() { if let Ok(angle) = node.to_angle() {
let degrees = angle.degrees(); let degrees = angle.degrees();

View file

@ -90,25 +90,23 @@ impl FontBaseSize {
impl FontRelativeLength { impl FontRelativeLength {
/// Return true if this is a zero value. /// Return true if this is a zero value.
fn is_zero(&self) -> bool { fn is_zero(&self) -> bool {
self.unitless_value() == 0.
}
/// Return the unitless, raw value.
fn unitless_value(&self) -> CSSFloat {
match *self { match *self {
FontRelativeLength::Em(v) | FontRelativeLength::Em(v) |
FontRelativeLength::Ex(v) | FontRelativeLength::Ex(v) |
FontRelativeLength::Ch(v) | FontRelativeLength::Ch(v) |
FontRelativeLength::Cap(v) | FontRelativeLength::Cap(v) |
FontRelativeLength::Ic(v) | FontRelativeLength::Ic(v) |
FontRelativeLength::Rem(v) => v == 0., FontRelativeLength::Rem(v) => v,
} }
} }
fn is_negative(&self) -> bool { fn is_negative(&self) -> bool {
match *self { self.unitless_value() < 0.
FontRelativeLength::Em(v) |
FontRelativeLength::Ex(v) |
FontRelativeLength::Ch(v) |
FontRelativeLength::Cap(v) |
FontRelativeLength::Ic(v) |
FontRelativeLength::Rem(v) => v < 0.,
}
} }
fn try_sum(&self, other: &Self) -> Result<Self, ()> { fn try_sum(&self, other: &Self) -> Result<Self, ()> {
@ -388,13 +386,16 @@ pub enum ViewportPercentageLength {
impl ViewportPercentageLength { impl ViewportPercentageLength {
/// Return true if this is a zero value. /// Return true if this is a zero value.
fn is_zero(&self) -> bool { fn is_zero(&self) -> bool {
let (_, _, v) = self.unpack(); self.unitless_value() == 0.
v == 0.
} }
fn is_negative(&self) -> bool { fn is_negative(&self) -> bool {
let (_, _, v) = self.unpack(); self.unitless_value() < 0.
v < 0. }
/// Return the unitless, raw value.
fn unitless_value(&self) -> CSSFloat {
self.unpack().2
} }
fn unpack(&self) -> (ViewportVariant, ViewportUnit, CSSFloat) { fn unpack(&self) -> (ViewportVariant, ViewportUnit, CSSFloat) {
@ -642,7 +643,8 @@ pub enum AbsoluteLength {
} }
impl AbsoluteLength { impl AbsoluteLength {
fn is_zero(&self) -> bool { /// Return the unitless, raw value.
fn unitless_value(&self) -> CSSFloat {
match *self { match *self {
AbsoluteLength::Px(v) | AbsoluteLength::Px(v) |
AbsoluteLength::In(v) | AbsoluteLength::In(v) |
@ -650,20 +652,16 @@ impl AbsoluteLength {
AbsoluteLength::Mm(v) | AbsoluteLength::Mm(v) |
AbsoluteLength::Q(v) | AbsoluteLength::Q(v) |
AbsoluteLength::Pt(v) | AbsoluteLength::Pt(v) |
AbsoluteLength::Pc(v) => v == 0., AbsoluteLength::Pc(v) => v,
} }
} }
fn is_zero(&self) -> bool {
self.unitless_value() == 0.
}
fn is_negative(&self) -> bool { fn is_negative(&self) -> bool {
match *self { self.unitless_value() < 0.
AbsoluteLength::Px(v) |
AbsoluteLength::In(v) |
AbsoluteLength::Cm(v) |
AbsoluteLength::Mm(v) |
AbsoluteLength::Q(v) |
AbsoluteLength::Pt(v) |
AbsoluteLength::Pc(v) => v < 0.,
}
} }
/// Convert this into a pixel value. /// Convert this into a pixel value.
@ -780,6 +778,16 @@ impl Mul<CSSFloat> for NoCalcLength {
} }
impl NoCalcLength { impl NoCalcLength {
/// Return the unitless, raw value.
pub fn unitless_value(&self) -> CSSFloat {
match *self {
NoCalcLength::Absolute(v) => v.unitless_value(),
NoCalcLength::FontRelative(v) => v.unitless_value(),
NoCalcLength::ViewportPercentage(v) => v.unitless_value(),
NoCalcLength::ServoCharacterWidth(c) => c.0 as f32,
}
}
/// Returns whether the value of this length without unit is less than zero. /// Returns whether the value of this length without unit is less than zero.
pub fn is_negative(&self) -> bool { pub fn is_negative(&self) -> bool {
match *self { match *self {