From cbd392f4e7c616a0c40754f4419c55eb3362ea49 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 19 Sep 2022 19:39:34 -0700 Subject: [PATCH] Support parsing angles with unitless zero exceptions Fixes #298 --- src/lib.rs | 11 ++++++++++ src/properties/effects.rs | 3 ++- src/properties/transform.rs | 42 ++++++++++++++++++------------------- src/values/angle.rs | 31 +++++++++++++++++++++++++++ src/values/gradient.rs | 8 +++++-- 5 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 066ee04f..9634848d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8710,6 +8710,8 @@ mod tests { minify_test(".foo { transform: rotateX(405deg)}", ".foo{transform:rotateX(405deg)}"); minify_test(".foo { transform: rotateY(405deg)}", ".foo{transform:rotateY(405deg)}"); minify_test(".foo { transform: rotate(-200deg)}", ".foo{transform:rotate(-200deg)}"); + minify_test(".foo { transform: rotate(0)", ".foo{transform:rotate(0)}"); + minify_test(".foo { transform: rotate(0deg)", ".foo{transform:rotate(0)}"); minify_test( ".foo { transform: rotateX(-200deg)}", ".foo{transform:rotateX(-200deg)}", @@ -8969,6 +8971,10 @@ mod tests { ".foo { background: linear-gradient(yellow, red 30%, red 40%, blue); }", ".foo{background:linear-gradient(#ff0,red 30% 40%,#00f)}", ); + minify_test( + ".foo { background: linear-gradient(0, yellow, blue); }", + ".foo{background:linear-gradient(#00f,#ff0)}", + ); minify_test( ".foo { background: -webkit-linear-gradient(yellow, blue) }", ".foo{background:-webkit-linear-gradient(#ff0,#00f)}", @@ -9141,6 +9147,10 @@ mod tests { ".foo { background: conic-gradient(from 0deg, #f06, gold) }", ".foo{background:conic-gradient(#f06,gold)}", ); + minify_test( + ".foo { background: conic-gradient(from 0, #f06, gold) }", + ".foo{background:conic-gradient(#f06,gold)}", + ); minify_test( ".foo { background: conic-gradient(from 0deg at center, #f06, gold) }", ".foo{background:conic-gradient(#f06,gold)}", @@ -20620,6 +20630,7 @@ mod tests { ".foo { filter: contrast(175%) brightness(3%); }", ".foo{filter:contrast(175%)brightness(3%)}", ); + minify_test(".foo { filter: hue-rotate(0) }", ".foo{filter:hue-rotate()}"); prefix_test( ".foo { filter: blur(5px) }", diff --git a/src/properties/effects.rs b/src/properties/effects.rs index e267df0e..eab5fe81 100644 --- a/src/properties/effects.rs +++ b/src/properties/effects.rs @@ -73,7 +73,8 @@ impl<'i> Parse<'i> for Filter<'i> { }, "hue-rotate" => { input.parse_nested_block(|input| { - Ok(Filter::HueRotate(input.try_parse(Angle::parse).unwrap_or(Angle::Deg(0.0)))) + // Spec has an exception for unitless zero angles: https://github.com/w3c/fxtf-drafts/issues/228 + Ok(Filter::HueRotate(input.try_parse(Angle::parse_with_unitless_zero).unwrap_or(Angle::zero()))) }) }, "invert" => { diff --git a/src/properties/transform.rs b/src/properties/transform.rs index e2d24b88..258fd484 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -948,19 +948,19 @@ impl<'i> Parse<'i> for Transform { Ok(Transform::Scale3d(x, y, z)) }, "rotate" => { - let angle = Angle::parse(input)?; + let angle = Angle::parse_with_unitless_zero(input)?; Ok(Transform::Rotate(angle)) }, "rotatex" => { - let angle = Angle::parse(input)?; + let angle = Angle::parse_with_unitless_zero(input)?; Ok(Transform::RotateX(angle)) }, "rotatey" => { - let angle = Angle::parse(input)?; + let angle = Angle::parse_with_unitless_zero(input)?; Ok(Transform::RotateY(angle)) }, "rotatez" => { - let angle = Angle::parse(input)?; + let angle = Angle::parse_with_unitless_zero(input)?; Ok(Transform::RotateZ(angle)) }, "rotate3d" => { @@ -970,24 +970,24 @@ impl<'i> Parse<'i> for Transform { input.expect_comma()?; let z = f32::parse(input)?; input.expect_comma()?; - let angle = Angle::parse(input)?; + let angle = Angle::parse_with_unitless_zero(input)?; Ok(Transform::Rotate3d(x, y, z, angle)) }, "skew" => { - let x = Angle::parse(input)?; + let x = Angle::parse_with_unitless_zero(input)?; if input.try_parse(|input| input.expect_comma()).is_ok() { - let y = Angle::parse(input)?; + let y = Angle::parse_with_unitless_zero(input)?; Ok(Transform::Skew(x, y)) } else { Ok(Transform::Skew(x, Angle::Deg(0.0))) } }, "skewx" => { - let angle = Angle::parse(input)?; + let angle = Angle::parse_with_unitless_zero(input)?; Ok(Transform::SkewX(angle)) }, "skewy" => { - let angle = Angle::parse(input)?; + let angle = Angle::parse_with_unitless_zero(input)?; Ok(Transform::SkewY(angle)) }, "perspective" => { @@ -1135,37 +1135,37 @@ impl ToCss for Transform { } Rotate(angle) => { dest.write_str("rotate(")?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; dest.write_char(')') } RotateX(angle) => { dest.write_str("rotateX(")?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; dest.write_char(')') } RotateY(angle) => { dest.write_str("rotateY(")?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; dest.write_char(')') } RotateZ(angle) => { dest.write_str(if dest.minify { "rotate(" } else { "rotateZ(" })?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; dest.write_char(')') } Rotate3d(x, y, z, angle) => { if dest.minify && *x == 1.0 && *y == 0.0 && *z == 0.0 { // rotate3d(1, 0, 0, a) => rotateX(a) dest.write_str("rotateX(")?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; } else if dest.minify && *x == 0.0 && *y == 1.0 && *z == 0.0 { // rotate3d(0, 1, 0, a) => rotateY(a) dest.write_str("rotateY(")?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; } else if dest.minify && *x == 0.0 && *y == 0.0 && *z == 1.0 { // rotate3d(0, 0, 1, a) => rotate(a) dest.write_str("rotate(")?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; } else { dest.write_str("rotate3d(")?; x.to_css(dest)?; @@ -1174,32 +1174,32 @@ impl ToCss for Transform { dest.delim(',', false)?; z.to_css(dest)?; dest.delim(',', false)?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; } dest.write_char(')') } Skew(x, y) => { if dest.minify && x.is_zero() && !y.is_zero() { dest.write_str("skewY(")?; - y.to_css(dest)? + y.to_css_with_unitless_zero(dest)? } else { dest.write_str("skew(")?; x.to_css(dest)?; if !y.is_zero() { dest.delim(',', false)?; - y.to_css(dest)?; + y.to_css_with_unitless_zero(dest)?; } } dest.write_char(')') } SkewX(angle) => { dest.write_str(if dest.minify { "skew(" } else { "skewX(" })?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; dest.write_char(')') } SkewY(angle) => { dest.write_str("skewY(")?; - angle.to_css(dest)?; + angle.to_css_with_unitless_zero(dest)?; dest.write_char(')') } Perspective(len) => { diff --git a/src/values/angle.rs b/src/values/angle.rs index 126f0056..a5d1e23d 100644 --- a/src/values/angle.rs +++ b/src/values/angle.rs @@ -37,6 +37,22 @@ pub enum Angle { impl<'i> Parse<'i> for Angle { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { + Self::parse_internal(input, false) + } +} + +impl Angle { + /// Parses an angle, allowing unitless zero values. + pub fn parse_with_unitless_zero<'i, 't>( + input: &mut Parser<'i, 't>, + ) -> Result>> { + Self::parse_internal(input, true) + } + + fn parse_internal<'i, 't>( + input: &mut Parser<'i, 't>, + allow_unitless_zero: bool, + ) -> Result>> { match input.try_parse(Calc::parse) { Ok(Calc::Value(v)) => return Ok(*v), // Angles are always compatible, so they will always compute to a value. @@ -56,6 +72,7 @@ impl<'i> Parse<'i> for Angle { _ => return Err(location.new_unexpected_token_error(token.clone())), } } + Token::Number { value, .. } if value == 0.0 && allow_unitless_zero => Ok(Angle::zero()), ref token => return Err(location.new_unexpected_token_error(token.clone())), } } @@ -86,6 +103,20 @@ impl ToCss for Angle { } } +impl Angle { + /// Prints the angle, allowing unitless zero values. + pub fn to_css_with_unitless_zero(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { + if self.is_zero() { + (0.0).to_css(dest) + } else { + self.to_css(dest) + } + } +} + impl Angle { /// Returns the angle in radians. pub fn to_radians(&self) -> CSSNumber { diff --git a/src/values/gradient.rs b/src/values/gradient.rs index 810a4271..14bc0796 100644 --- a/src/values/gradient.rs +++ b/src/values/gradient.rs @@ -388,7 +388,9 @@ impl LineDirection { input: &mut Parser<'i, 't>, is_prefixed: bool, ) -> Result>> { - if let Ok(angle) = input.try_parse(Angle::parse) { + // Spec allows unitless zero angles for gradients. + // https://w3c.github.io/csswg-drafts/css-images-3/#linear-gradient-syntax + if let Ok(angle) = input.try_parse(Angle::parse_with_unitless_zero) { return Ok(LineDirection::Angle(angle)); } @@ -672,7 +674,9 @@ impl ConicGradient { fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result>> { let angle = input.try_parse(|input| { input.expect_ident_matching("from")?; - Angle::parse(input) + // Spec allows unitless zero angles for gradients. + // https://w3c.github.io/csswg-drafts/css-images-4/#valdef-conic-gradient-angle + Angle::parse_with_unitless_zero(input) }); let position = input.try_parse(|input| {