From 87629211286cb8d84e078c410b3b607c3f4f6a1e Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Tue, 13 Dec 2022 09:56:11 +0100 Subject: [PATCH 1/9] Add color() parsing and serialization --- src/color.rs | 135 +++++++++++++++++- .../color4_color_function.json | 3 + src/tests.rs | 16 +++ 3 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 src/css-parsing-tests/color4_color_function.json diff --git a/src/color.rs b/src/color.rs index b94b4aad..1a286814 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,8 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use std::f32::consts::PI; use std::fmt; +use std::{f32::consts::PI, str::FromStr}; use super::{BasicParseError, ParseError, Parser, ToCss, Token}; @@ -350,6 +350,81 @@ macro_rules! impl_lch_like { impl_lch_like!(Lch, "lch"); impl_lch_like!(Oklch, "oklch"); +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum PredefinedColorSpace { + Srgb, + SrgbLinear, + DisplayP3, + A98Rgb, + ProphotoRgb, + Rec2020, + XyzD50, + XyzD65, +} + +impl PredefinedColorSpace { + fn as_str(&self) -> &str { + match self { + PredefinedColorSpace::Srgb => "srgb", + PredefinedColorSpace::SrgbLinear => "srgb-linear", + PredefinedColorSpace::DisplayP3 => "display-p3", + PredefinedColorSpace::A98Rgb => "a98-rgb", + PredefinedColorSpace::ProphotoRgb => "prophoto-rgb", + PredefinedColorSpace::Rec2020 => "rec2020", + PredefinedColorSpace::XyzD50 => "xyz-d50", + PredefinedColorSpace::XyzD65 => "xyz-d65", + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for PredefinedColorSpace { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_str().serialize(serializer) + } +} + +impl ToCss for PredefinedColorSpace { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str(self.as_str()) + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct ColorFunction { + pub color_space: PredefinedColorSpace, + pub red: f32, + pub green: f32, + pub blue: f32, + pub alpha: f32, +} + +impl ToCss for ColorFunction { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str("color(")?; + self.color_space.to_css(dest)?; + dest.write_str(" ")?; + self.red.to_css(dest)?; + dest.write_str(" ")?; + self.green.to_css(dest)?; + dest.write_str(" ")?; + self.blue.to_css(dest)?; + + serialize_alpha(dest, self.alpha, false)?; + + dest.write_char(')') + } +} + /// An absolutely specified color. /// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-base #[derive(Clone, Copy, PartialEq, Debug)] @@ -370,6 +445,8 @@ pub enum AbsoluteColor { /// Specifies an Oklab color by Oklab Lightness, Chroma, and hue using /// the OKLCH cylindrical coordinate model. Oklch(Oklch), + /// Specified a sRGB based color with a predefined color space. + ColorFunction(ColorFunction), } impl AbsoluteColor { @@ -381,6 +458,7 @@ impl AbsoluteColor { Self::Lch(c) => c.alpha, Self::Oklab(c) => c.alpha, Self::Oklch(c) => c.alpha, + Self::ColorFunction(c) => c.alpha, } } } @@ -396,6 +474,7 @@ impl ToCss for AbsoluteColor { Self::Lch(lch) => lch.to_css(dest), Self::Oklab(lab) => lab.to_css(dest), Self::Oklch(lch) => lch.to_css(dest), + Self::ColorFunction(color_function) => color_function.to_css(dest), } } } @@ -571,7 +650,7 @@ impl Color { Token::Function(ref name) => { let name = name.clone(); return input.parse_nested_block(|arguments| { - parse_color_function(component_parser, &*name, arguments) + parse_color(component_parser, &*name, arguments) }); } _ => Err(()), @@ -785,7 +864,7 @@ fn clamp_floor_256_f32(val: f32) -> u8 { } #[inline] -fn parse_color_function<'i, 't, ComponentParser>( +fn parse_color<'i, 't, ComponentParser>( component_parser: &ComponentParser, name: &str, arguments: &mut Parser<'i, 't>, @@ -827,6 +906,8 @@ where Color::Absolute(AbsoluteColor::Oklch(Oklch::new(l.max(0.), c.max(0.), h, alpha))) }), + "color" => parse_color_function(component_parser, arguments), + _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name.to_owned().into()))), }?; @@ -1072,3 +1153,51 @@ where Ok(into_color(lightness, chroma, hue, alpha)) } + +#[inline] +fn parse_color_function<'i, 't, ComponentParser>( + component_parser: &ComponentParser, + arguments: &mut Parser<'i, 't>, +) -> Result> +where + ComponentParser: ColorComponentParser<'i>, +{ + let location = arguments.current_source_location(); + let color_space = arguments.try_parse(|i| { + let ident = i.expect_ident()?; + Ok(match_ignore_ascii_case! { + ident, + "srgb" => PredefinedColorSpace::Srgb, + "srgb-linear" => PredefinedColorSpace::SrgbLinear, + "display-p3" => PredefinedColorSpace::DisplayP3, + "a98-rgb" => PredefinedColorSpace::A98Rgb, + "prophoto-rgb" => PredefinedColorSpace::ProphotoRgb, + "rec2020" => PredefinedColorSpace::Rec2020, + "xyz-d50" => PredefinedColorSpace::XyzD50, + "xyz-d65" => PredefinedColorSpace::XyzD65, + _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))) + }) + })?; + + let red = component_parser + .parse_number_or_percentage(arguments)? + .unit_value(); + let green = component_parser + .parse_number_or_percentage(arguments)? + .unit_value(); + let blue = component_parser + .parse_number_or_percentage(arguments)? + .unit_value(); + + let alpha = parse_alpha(component_parser, arguments, false)?; + + Ok(Color::Absolute(AbsoluteColor::ColorFunction( + ColorFunction { + color_space, + red, + green, + blue, + alpha, + }, + ))) +} diff --git a/src/css-parsing-tests/color4_color_function.json b/src/css-parsing-tests/color4_color_function.json new file mode 100644 index 00000000..a28bb619 --- /dev/null +++ b/src/css-parsing-tests/color4_color_function.json @@ -0,0 +1,3 @@ +[ + "color(srgb 1 1 1 / 0.5)", "color(srgb 1 1 1 / 0.5)" +] diff --git a/src/tests.rs b/src/tests.rs index b4d25626..da2bb3de 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -404,6 +404,18 @@ fn color4_lab_lch_oklab_oklch() { ) } +#[test] +fn color4_color_function() { + run_color_tests( + include_str!("css-parsing-tests/color4_color_function.json"), + |c| { + c.ok() + .map(|v| v.to_css_string().to_json()) + .unwrap_or(Value::Null) + }, + ) +} + #[test] fn nth() { run_json_tests(include_str!("css-parsing-tests/An+B.json"), |input| { @@ -854,6 +866,7 @@ where } } +#[cfg(feature = "serde")] impl ToJson for Color { fn to_json(&self) -> Value { match *self { @@ -866,6 +879,9 @@ impl ToJson for Color { AbsoluteColor::Lch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]), AbsoluteColor::Oklab(ref c) => json!([c.lightness, c.a, c.b, c.alpha]), AbsoluteColor::Oklch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]), + AbsoluteColor::ColorFunction(ref c) => { + json!([c.color_space, c.red, c.green, c.blue, c.alpha]) + } }, } } From 5bd00bebd2321e9b10767b5a8a204df025ad74cf Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Tue, 13 Dec 2022 10:38:16 +0100 Subject: [PATCH 2/9] Add tests for color() --- src/color.rs | 46 ++-- .../color4_color_function.json | 235 +++++++++++++++++- .../make_color4_color_function.py | 58 +++++ 3 files changed, 317 insertions(+), 22 deletions(-) create mode 100644 src/css-parsing-tests/make_color4_color_function.py diff --git a/src/color.rs b/src/color.rs index 1a286814..acc71cfd 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,8 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use std::f32::consts::PI; use std::fmt; -use std::{f32::consts::PI, str::FromStr}; use super::{BasicParseError, ParseError, Parser, ToCss, Token}; @@ -399,9 +399,9 @@ impl ToCss for PredefinedColorSpace { #[derive(Clone, Copy, PartialEq, Debug)] pub struct ColorFunction { pub color_space: PredefinedColorSpace, - pub red: f32, - pub green: f32, - pub blue: f32, + pub c1: f32, + pub c2: f32, + pub c3: f32, pub alpha: f32, } @@ -413,11 +413,11 @@ impl ToCss for ColorFunction { dest.write_str("color(")?; self.color_space.to_css(dest)?; dest.write_str(" ")?; - self.red.to_css(dest)?; + self.c1.to_css(dest)?; dest.write_str(" ")?; - self.green.to_css(dest)?; + self.c2.to_css(dest)?; dest.write_str(" ")?; - self.blue.to_css(dest)?; + self.c3.to_css(dest)?; serialize_alpha(dest, self.alpha, false)?; @@ -445,7 +445,7 @@ pub enum AbsoluteColor { /// Specifies an Oklab color by Oklab Lightness, Chroma, and hue using /// the OKLCH cylindrical coordinate model. Oklch(Oklch), - /// Specified a sRGB based color with a predefined color space. + /// Specifies a color in a predefined color space. ColorFunction(ColorFunction), } @@ -1174,29 +1174,33 @@ where "prophoto-rgb" => PredefinedColorSpace::ProphotoRgb, "rec2020" => PredefinedColorSpace::Rec2020, "xyz-d50" => PredefinedColorSpace::XyzD50, - "xyz-d65" => PredefinedColorSpace::XyzD65, + "xyz" | "xyz-d65" => PredefinedColorSpace::XyzD65, _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))) }) })?; - let red = component_parser - .parse_number_or_percentage(arguments)? - .unit_value(); - let green = component_parser - .parse_number_or_percentage(arguments)? - .unit_value(); - let blue = component_parser - .parse_number_or_percentage(arguments)? - .unit_value(); + macro_rules! parse_component { + () => {{ + if let Ok(c) = arguments.try_parse(|i| component_parser.parse_number_or_percentage(i)) { + c.unit_value() + } else { + 0.0 + } + }}; + } + + let c1 = parse_component!(); + let c2 = parse_component!(); + let c3 = parse_component!(); let alpha = parse_alpha(component_parser, arguments, false)?; Ok(Color::Absolute(AbsoluteColor::ColorFunction( ColorFunction { color_space, - red, - green, - blue, + c1, + c2, + c3, alpha, }, ))) diff --git a/src/css-parsing-tests/color4_color_function.json b/src/css-parsing-tests/color4_color_function.json index a28bb619..a2464d1d 100644 --- a/src/css-parsing-tests/color4_color_function.json +++ b/src/css-parsing-tests/color4_color_function.json @@ -1,3 +1,236 @@ [ - "color(srgb 1 1 1 / 0.5)", "color(srgb 1 1 1 / 0.5)" + "color(srgb 0% 0% 0%)", "color(srgb 0 0 0)", + "color(srgb 10% 10% 10%)", "color(srgb 0.1 0.1 0.1)", + "color(srgb .2 .2 25%)", "color(srgb 0.2 0.2 0.25)", + "color(srgb 0 0 0 / 1)", "color(srgb 0 0 0)", + "color(srgb 0% 0 0 / 0.5)", "color(srgb 0 0 0 / 0.5)", + "color(srgb 20% 0 10/0.5)", "color(srgb 0.2 0 10 / 0.5)", + "color(srgb 20% 0 10/50%)", "color(srgb 0.2 0 10 / 0.5)", + "color(srgb 400% 0 10/50%)", "color(srgb 4 0 10 / 0.5)", + "color(srgb 50% -160 160)", "color(srgb 0.5 -160 160)", + "color(srgb 50% -200 200)", "color(srgb 0.5 -200 200)", + "color(srgb 0 0 0 / -10%)", "color(srgb 0 0 0 / 0)", + "color(srgb 0 0 0 / 110%)", "color(srgb 0 0 0)", + "color(srgb 0 0 0 / 300%)", "color(srgb 0 0 0)", + "color(srgb 50% -200)", "color(srgb 0.5 -200 0)", + "color(srgb 50%)", "color(srgb 0.5 0 0)", + "color(srgb )", "color(srgb 0 0 0)", + "color(srgb 50% -200 / 0.5)", "color(srgb 0.5 -200 0 / 0.5)", + "color(srgb 50% / 0.5)", "color(srgb 0.5 0 0 / 0.5)", + "color(srgb / 0.5)", "color(srgb 0 0 0 / 0.5)", + "color(srgb 200 200 200)", "color(srgb 200 200 200)", + "color(srgb 200 200 200 / 200)", "color(srgb 200 200 200)", + "color(srgb -200 -200 -200)", "color(srgb -200 -200 -200)", + "color(srgb -200 -200 -200 / -200)", "color(srgb -200 -200 -200 / 0)", + "color(srgb 200% 200% 200%)", "color(srgb 2 2 2)", + "color(srgb 200% 200% 200% / 200%)", "color(srgb 2 2 2)", + "color(srgb -200% -200% -200% / -200%)", "color(srgb -2 -2 -2 / 0)", + "color(srgb-linear 0% 0% 0%)", "color(srgb-linear 0 0 0)", + "color(srgb-linear 10% 10% 10%)", "color(srgb-linear 0.1 0.1 0.1)", + "color(srgb-linear .2 .2 25%)", "color(srgb-linear 0.2 0.2 0.25)", + "color(srgb-linear 0 0 0 / 1)", "color(srgb-linear 0 0 0)", + "color(srgb-linear 0% 0 0 / 0.5)", "color(srgb-linear 0 0 0 / 0.5)", + "color(srgb-linear 20% 0 10/0.5)", "color(srgb-linear 0.2 0 10 / 0.5)", + "color(srgb-linear 20% 0 10/50%)", "color(srgb-linear 0.2 0 10 / 0.5)", + "color(srgb-linear 400% 0 10/50%)", "color(srgb-linear 4 0 10 / 0.5)", + "color(srgb-linear 50% -160 160)", "color(srgb-linear 0.5 -160 160)", + "color(srgb-linear 50% -200 200)", "color(srgb-linear 0.5 -200 200)", + "color(srgb-linear 0 0 0 / -10%)", "color(srgb-linear 0 0 0 / 0)", + "color(srgb-linear 0 0 0 / 110%)", "color(srgb-linear 0 0 0)", + "color(srgb-linear 0 0 0 / 300%)", "color(srgb-linear 0 0 0)", + "color(srgb-linear 50% -200)", "color(srgb-linear 0.5 -200 0)", + "color(srgb-linear 50%)", "color(srgb-linear 0.5 0 0)", + "color(srgb-linear )", "color(srgb-linear 0 0 0)", + "color(srgb-linear 50% -200 / 0.5)", "color(srgb-linear 0.5 -200 0 / 0.5)", + "color(srgb-linear 50% / 0.5)", "color(srgb-linear 0.5 0 0 / 0.5)", + "color(srgb-linear / 0.5)", "color(srgb-linear 0 0 0 / 0.5)", + "color(srgb-linear 200 200 200)", "color(srgb-linear 200 200 200)", + "color(srgb-linear 200 200 200 / 200)", "color(srgb-linear 200 200 200)", + "color(srgb-linear -200 -200 -200)", "color(srgb-linear -200 -200 -200)", + "color(srgb-linear -200 -200 -200 / -200)", "color(srgb-linear -200 -200 -200 / 0)", + "color(srgb-linear 200% 200% 200%)", "color(srgb-linear 2 2 2)", + "color(srgb-linear 200% 200% 200% / 200%)", "color(srgb-linear 2 2 2)", + "color(srgb-linear -200% -200% -200% / -200%)", "color(srgb-linear -2 -2 -2 / 0)", + "color(display-p3 0% 0% 0%)", "color(display-p3 0 0 0)", + "color(display-p3 10% 10% 10%)", "color(display-p3 0.1 0.1 0.1)", + "color(display-p3 .2 .2 25%)", "color(display-p3 0.2 0.2 0.25)", + "color(display-p3 0 0 0 / 1)", "color(display-p3 0 0 0)", + "color(display-p3 0% 0 0 / 0.5)", "color(display-p3 0 0 0 / 0.5)", + "color(display-p3 20% 0 10/0.5)", "color(display-p3 0.2 0 10 / 0.5)", + "color(display-p3 20% 0 10/50%)", "color(display-p3 0.2 0 10 / 0.5)", + "color(display-p3 400% 0 10/50%)", "color(display-p3 4 0 10 / 0.5)", + "color(display-p3 50% -160 160)", "color(display-p3 0.5 -160 160)", + "color(display-p3 50% -200 200)", "color(display-p3 0.5 -200 200)", + "color(display-p3 0 0 0 / -10%)", "color(display-p3 0 0 0 / 0)", + "color(display-p3 0 0 0 / 110%)", "color(display-p3 0 0 0)", + "color(display-p3 0 0 0 / 300%)", "color(display-p3 0 0 0)", + "color(display-p3 50% -200)", "color(display-p3 0.5 -200 0)", + "color(display-p3 50%)", "color(display-p3 0.5 0 0)", + "color(display-p3 )", "color(display-p3 0 0 0)", + "color(display-p3 50% -200 / 0.5)", "color(display-p3 0.5 -200 0 / 0.5)", + "color(display-p3 50% / 0.5)", "color(display-p3 0.5 0 0 / 0.5)", + "color(display-p3 / 0.5)", "color(display-p3 0 0 0 / 0.5)", + "color(display-p3 200 200 200)", "color(display-p3 200 200 200)", + "color(display-p3 200 200 200 / 200)", "color(display-p3 200 200 200)", + "color(display-p3 -200 -200 -200)", "color(display-p3 -200 -200 -200)", + "color(display-p3 -200 -200 -200 / -200)", "color(display-p3 -200 -200 -200 / 0)", + "color(display-p3 200% 200% 200%)", "color(display-p3 2 2 2)", + "color(display-p3 200% 200% 200% / 200%)", "color(display-p3 2 2 2)", + "color(display-p3 -200% -200% -200% / -200%)", "color(display-p3 -2 -2 -2 / 0)", + "color(a98-rgb 0% 0% 0%)", "color(a98-rgb 0 0 0)", + "color(a98-rgb 10% 10% 10%)", "color(a98-rgb 0.1 0.1 0.1)", + "color(a98-rgb .2 .2 25%)", "color(a98-rgb 0.2 0.2 0.25)", + "color(a98-rgb 0 0 0 / 1)", "color(a98-rgb 0 0 0)", + "color(a98-rgb 0% 0 0 / 0.5)", "color(a98-rgb 0 0 0 / 0.5)", + "color(a98-rgb 20% 0 10/0.5)", "color(a98-rgb 0.2 0 10 / 0.5)", + "color(a98-rgb 20% 0 10/50%)", "color(a98-rgb 0.2 0 10 / 0.5)", + "color(a98-rgb 400% 0 10/50%)", "color(a98-rgb 4 0 10 / 0.5)", + "color(a98-rgb 50% -160 160)", "color(a98-rgb 0.5 -160 160)", + "color(a98-rgb 50% -200 200)", "color(a98-rgb 0.5 -200 200)", + "color(a98-rgb 0 0 0 / -10%)", "color(a98-rgb 0 0 0 / 0)", + "color(a98-rgb 0 0 0 / 110%)", "color(a98-rgb 0 0 0)", + "color(a98-rgb 0 0 0 / 300%)", "color(a98-rgb 0 0 0)", + "color(a98-rgb 50% -200)", "color(a98-rgb 0.5 -200 0)", + "color(a98-rgb 50%)", "color(a98-rgb 0.5 0 0)", + "color(a98-rgb )", "color(a98-rgb 0 0 0)", + "color(a98-rgb 50% -200 / 0.5)", "color(a98-rgb 0.5 -200 0 / 0.5)", + "color(a98-rgb 50% / 0.5)", "color(a98-rgb 0.5 0 0 / 0.5)", + "color(a98-rgb / 0.5)", "color(a98-rgb 0 0 0 / 0.5)", + "color(a98-rgb 200 200 200)", "color(a98-rgb 200 200 200)", + "color(a98-rgb 200 200 200 / 200)", "color(a98-rgb 200 200 200)", + "color(a98-rgb -200 -200 -200)", "color(a98-rgb -200 -200 -200)", + "color(a98-rgb -200 -200 -200 / -200)", "color(a98-rgb -200 -200 -200 / 0)", + "color(a98-rgb 200% 200% 200%)", "color(a98-rgb 2 2 2)", + "color(a98-rgb 200% 200% 200% / 200%)", "color(a98-rgb 2 2 2)", + "color(a98-rgb -200% -200% -200% / -200%)", "color(a98-rgb -2 -2 -2 / 0)", + "color(prophoto-rgb 0% 0% 0%)", "color(prophoto-rgb 0 0 0)", + "color(prophoto-rgb 10% 10% 10%)", "color(prophoto-rgb 0.1 0.1 0.1)", + "color(prophoto-rgb .2 .2 25%)", "color(prophoto-rgb 0.2 0.2 0.25)", + "color(prophoto-rgb 0 0 0 / 1)", "color(prophoto-rgb 0 0 0)", + "color(prophoto-rgb 0% 0 0 / 0.5)", "color(prophoto-rgb 0 0 0 / 0.5)", + "color(prophoto-rgb 20% 0 10/0.5)", "color(prophoto-rgb 0.2 0 10 / 0.5)", + "color(prophoto-rgb 20% 0 10/50%)", "color(prophoto-rgb 0.2 0 10 / 0.5)", + "color(prophoto-rgb 400% 0 10/50%)", "color(prophoto-rgb 4 0 10 / 0.5)", + "color(prophoto-rgb 50% -160 160)", "color(prophoto-rgb 0.5 -160 160)", + "color(prophoto-rgb 50% -200 200)", "color(prophoto-rgb 0.5 -200 200)", + "color(prophoto-rgb 0 0 0 / -10%)", "color(prophoto-rgb 0 0 0 / 0)", + "color(prophoto-rgb 0 0 0 / 110%)", "color(prophoto-rgb 0 0 0)", + "color(prophoto-rgb 0 0 0 / 300%)", "color(prophoto-rgb 0 0 0)", + "color(prophoto-rgb 50% -200)", "color(prophoto-rgb 0.5 -200 0)", + "color(prophoto-rgb 50%)", "color(prophoto-rgb 0.5 0 0)", + "color(prophoto-rgb )", "color(prophoto-rgb 0 0 0)", + "color(prophoto-rgb 50% -200 / 0.5)", "color(prophoto-rgb 0.5 -200 0 / 0.5)", + "color(prophoto-rgb 50% / 0.5)", "color(prophoto-rgb 0.5 0 0 / 0.5)", + "color(prophoto-rgb / 0.5)", "color(prophoto-rgb 0 0 0 / 0.5)", + "color(prophoto-rgb 200 200 200)", "color(prophoto-rgb 200 200 200)", + "color(prophoto-rgb 200 200 200 / 200)", "color(prophoto-rgb 200 200 200)", + "color(prophoto-rgb -200 -200 -200)", "color(prophoto-rgb -200 -200 -200)", + "color(prophoto-rgb -200 -200 -200 / -200)", "color(prophoto-rgb -200 -200 -200 / 0)", + "color(prophoto-rgb 200% 200% 200%)", "color(prophoto-rgb 2 2 2)", + "color(prophoto-rgb 200% 200% 200% / 200%)", "color(prophoto-rgb 2 2 2)", + "color(prophoto-rgb -200% -200% -200% / -200%)", "color(prophoto-rgb -2 -2 -2 / 0)", + "color(rec2020 0% 0% 0%)", "color(rec2020 0 0 0)", + "color(rec2020 10% 10% 10%)", "color(rec2020 0.1 0.1 0.1)", + "color(rec2020 .2 .2 25%)", "color(rec2020 0.2 0.2 0.25)", + "color(rec2020 0 0 0 / 1)", "color(rec2020 0 0 0)", + "color(rec2020 0% 0 0 / 0.5)", "color(rec2020 0 0 0 / 0.5)", + "color(rec2020 20% 0 10/0.5)", "color(rec2020 0.2 0 10 / 0.5)", + "color(rec2020 20% 0 10/50%)", "color(rec2020 0.2 0 10 / 0.5)", + "color(rec2020 400% 0 10/50%)", "color(rec2020 4 0 10 / 0.5)", + "color(rec2020 50% -160 160)", "color(rec2020 0.5 -160 160)", + "color(rec2020 50% -200 200)", "color(rec2020 0.5 -200 200)", + "color(rec2020 0 0 0 / -10%)", "color(rec2020 0 0 0 / 0)", + "color(rec2020 0 0 0 / 110%)", "color(rec2020 0 0 0)", + "color(rec2020 0 0 0 / 300%)", "color(rec2020 0 0 0)", + "color(rec2020 50% -200)", "color(rec2020 0.5 -200 0)", + "color(rec2020 50%)", "color(rec2020 0.5 0 0)", + "color(rec2020 )", "color(rec2020 0 0 0)", + "color(rec2020 50% -200 / 0.5)", "color(rec2020 0.5 -200 0 / 0.5)", + "color(rec2020 50% / 0.5)", "color(rec2020 0.5 0 0 / 0.5)", + "color(rec2020 / 0.5)", "color(rec2020 0 0 0 / 0.5)", + "color(rec2020 200 200 200)", "color(rec2020 200 200 200)", + "color(rec2020 200 200 200 / 200)", "color(rec2020 200 200 200)", + "color(rec2020 -200 -200 -200)", "color(rec2020 -200 -200 -200)", + "color(rec2020 -200 -200 -200 / -200)", "color(rec2020 -200 -200 -200 / 0)", + "color(rec2020 200% 200% 200%)", "color(rec2020 2 2 2)", + "color(rec2020 200% 200% 200% / 200%)", "color(rec2020 2 2 2)", + "color(rec2020 -200% -200% -200% / -200%)", "color(rec2020 -2 -2 -2 / 0)", + "color(xyz 0% 0% 0%)", "color(xyz-d65 0 0 0)", + "color(xyz 10% 10% 10%)", "color(xyz-d65 0.1 0.1 0.1)", + "color(xyz .2 .2 25%)", "color(xyz-d65 0.2 0.2 0.25)", + "color(xyz 0 0 0 / 1)", "color(xyz-d65 0 0 0)", + "color(xyz 0% 0 0 / 0.5)", "color(xyz-d65 0 0 0 / 0.5)", + "color(xyz 20% 0 10/0.5)", "color(xyz-d65 0.2 0 10 / 0.5)", + "color(xyz 20% 0 10/50%)", "color(xyz-d65 0.2 0 10 / 0.5)", + "color(xyz 400% 0 10/50%)", "color(xyz-d65 4 0 10 / 0.5)", + "color(xyz 50% -160 160)", "color(xyz-d65 0.5 -160 160)", + "color(xyz 50% -200 200)", "color(xyz-d65 0.5 -200 200)", + "color(xyz 0 0 0 / -10%)", "color(xyz-d65 0 0 0 / 0)", + "color(xyz 0 0 0 / 110%)", "color(xyz-d65 0 0 0)", + "color(xyz 0 0 0 / 300%)", "color(xyz-d65 0 0 0)", + "color(xyz 50% -200)", "color(xyz-d65 0.5 -200 0)", + "color(xyz 50%)", "color(xyz-d65 0.5 0 0)", + "color(xyz )", "color(xyz-d65 0 0 0)", + "color(xyz 50% -200 / 0.5)", "color(xyz-d65 0.5 -200 0 / 0.5)", + "color(xyz 50% / 0.5)", "color(xyz-d65 0.5 0 0 / 0.5)", + "color(xyz / 0.5)", "color(xyz-d65 0 0 0 / 0.5)", + "color(xyz 200 200 200)", "color(xyz-d65 200 200 200)", + "color(xyz 200 200 200 / 200)", "color(xyz-d65 200 200 200)", + "color(xyz -200 -200 -200)", "color(xyz-d65 -200 -200 -200)", + "color(xyz -200 -200 -200 / -200)", "color(xyz-d65 -200 -200 -200 / 0)", + "color(xyz 200% 200% 200%)", "color(xyz-d65 2 2 2)", + "color(xyz 200% 200% 200% / 200%)", "color(xyz-d65 2 2 2)", + "color(xyz -200% -200% -200% / -200%)", "color(xyz-d65 -2 -2 -2 / 0)", + "color(xyz-d50 0% 0% 0%)", "color(xyz-d50 0 0 0)", + "color(xyz-d50 10% 10% 10%)", "color(xyz-d50 0.1 0.1 0.1)", + "color(xyz-d50 .2 .2 25%)", "color(xyz-d50 0.2 0.2 0.25)", + "color(xyz-d50 0 0 0 / 1)", "color(xyz-d50 0 0 0)", + "color(xyz-d50 0% 0 0 / 0.5)", "color(xyz-d50 0 0 0 / 0.5)", + "color(xyz-d50 20% 0 10/0.5)", "color(xyz-d50 0.2 0 10 / 0.5)", + "color(xyz-d50 20% 0 10/50%)", "color(xyz-d50 0.2 0 10 / 0.5)", + "color(xyz-d50 400% 0 10/50%)", "color(xyz-d50 4 0 10 / 0.5)", + "color(xyz-d50 50% -160 160)", "color(xyz-d50 0.5 -160 160)", + "color(xyz-d50 50% -200 200)", "color(xyz-d50 0.5 -200 200)", + "color(xyz-d50 0 0 0 / -10%)", "color(xyz-d50 0 0 0 / 0)", + "color(xyz-d50 0 0 0 / 110%)", "color(xyz-d50 0 0 0)", + "color(xyz-d50 0 0 0 / 300%)", "color(xyz-d50 0 0 0)", + "color(xyz-d50 50% -200)", "color(xyz-d50 0.5 -200 0)", + "color(xyz-d50 50%)", "color(xyz-d50 0.5 0 0)", + "color(xyz-d50 )", "color(xyz-d50 0 0 0)", + "color(xyz-d50 50% -200 / 0.5)", "color(xyz-d50 0.5 -200 0 / 0.5)", + "color(xyz-d50 50% / 0.5)", "color(xyz-d50 0.5 0 0 / 0.5)", + "color(xyz-d50 / 0.5)", "color(xyz-d50 0 0 0 / 0.5)", + "color(xyz-d50 200 200 200)", "color(xyz-d50 200 200 200)", + "color(xyz-d50 200 200 200 / 200)", "color(xyz-d50 200 200 200)", + "color(xyz-d50 -200 -200 -200)", "color(xyz-d50 -200 -200 -200)", + "color(xyz-d50 -200 -200 -200 / -200)", "color(xyz-d50 -200 -200 -200 / 0)", + "color(xyz-d50 200% 200% 200%)", "color(xyz-d50 2 2 2)", + "color(xyz-d50 200% 200% 200% / 200%)", "color(xyz-d50 2 2 2)", + "color(xyz-d50 -200% -200% -200% / -200%)", "color(xyz-d50 -2 -2 -2 / 0)", + "color(xyz-d65 0% 0% 0%)", "color(xyz-d65 0 0 0)", + "color(xyz-d65 10% 10% 10%)", "color(xyz-d65 0.1 0.1 0.1)", + "color(xyz-d65 .2 .2 25%)", "color(xyz-d65 0.2 0.2 0.25)", + "color(xyz-d65 0 0 0 / 1)", "color(xyz-d65 0 0 0)", + "color(xyz-d65 0% 0 0 / 0.5)", "color(xyz-d65 0 0 0 / 0.5)", + "color(xyz-d65 20% 0 10/0.5)", "color(xyz-d65 0.2 0 10 / 0.5)", + "color(xyz-d65 20% 0 10/50%)", "color(xyz-d65 0.2 0 10 / 0.5)", + "color(xyz-d65 400% 0 10/50%)", "color(xyz-d65 4 0 10 / 0.5)", + "color(xyz-d65 50% -160 160)", "color(xyz-d65 0.5 -160 160)", + "color(xyz-d65 50% -200 200)", "color(xyz-d65 0.5 -200 200)", + "color(xyz-d65 0 0 0 / -10%)", "color(xyz-d65 0 0 0 / 0)", + "color(xyz-d65 0 0 0 / 110%)", "color(xyz-d65 0 0 0)", + "color(xyz-d65 0 0 0 / 300%)", "color(xyz-d65 0 0 0)", + "color(xyz-d65 50% -200)", "color(xyz-d65 0.5 -200 0)", + "color(xyz-d65 50%)", "color(xyz-d65 0.5 0 0)", + "color(xyz-d65 )", "color(xyz-d65 0 0 0)", + "color(xyz-d65 50% -200 / 0.5)", "color(xyz-d65 0.5 -200 0 / 0.5)", + "color(xyz-d65 50% / 0.5)", "color(xyz-d65 0.5 0 0 / 0.5)", + "color(xyz-d65 / 0.5)", "color(xyz-d65 0 0 0 / 0.5)", + "color(xyz-d65 200 200 200)", "color(xyz-d65 200 200 200)", + "color(xyz-d65 200 200 200 / 200)", "color(xyz-d65 200 200 200)", + "color(xyz-d65 -200 -200 -200)", "color(xyz-d65 -200 -200 -200)", + "color(xyz-d65 -200 -200 -200 / -200)", "color(xyz-d65 -200 -200 -200 / 0)", + "color(xyz-d65 200% 200% 200%)", "color(xyz-d65 2 2 2)", + "color(xyz-d65 200% 200% 200% / 200%)", "color(xyz-d65 2 2 2)", + "color(xyz-d65 -200% -200% -200% / -200%)", "color(xyz-d65 -2 -2 -2 / 0)" ] diff --git a/src/css-parsing-tests/make_color4_color_function.py b/src/css-parsing-tests/make_color4_color_function.py new file mode 100644 index 00000000..5b8c01b0 --- /dev/null +++ b/src/css-parsing-tests/make_color4_color_function.py @@ -0,0 +1,58 @@ +COLOR_SPACES = [ + 'srgb', + 'srgb-linear', + 'display-p3', + 'a98-rgb', + 'prophoto-rgb', + 'rec2020', + 'xyz', + 'xyz-d50', + 'xyz-d65' +] +PERMUTATIONS = [ + ('0% 0% 0%', '0 0 0'), + ('10% 10% 10%', '0.1 0.1 0.1'), + ('.2 .2 25%', '0.2 0.2 0.25'), + ('0 0 0 / 1', '0 0 0'), + ('0% 0 0 / 0.5', '0 0 0 / 0.5'), + ('20% 0 10/0.5', '0.2 0 10 / 0.5'), + ('20% 0 10/50%', '0.2 0 10 / 0.5'), + ('400% 0 10/50%', '4 0 10 / 0.5'), + ('50% -160 160', '0.5 -160 160'), + ('50% -200 200', '0.5 -200 200'), + ('0 0 0 / -10%', '0 0 0 / 0'), + ('0 0 0 / 110%', '0 0 0'), + ('0 0 0 / 300%', '0 0 0'), + ('50% -200', '0.5 -200 0'), + ('50%', '0.5 0 0'), + ('', '0 0 0'), + ('50% -200 / 0.5', '0.5 -200 0 / 0.5'), + ('50% / 0.5', '0.5 0 0 / 0.5'), + ('/ 0.5', '0 0 0 / 0.5'), + ('200 200 200', '200 200 200'), + ('200 200 200 / 200', '200 200 200'), + ('-200 -200 -200', '-200 -200 -200'), + ('-200 -200 -200 / -200', '-200 -200 -200 / 0'), + ('200% 200% 200%', '2 2 2'), + ('200% 200% 200% / 200%', '2 2 2'), + ('-200% -200% -200% / -200%', '-2 -2 -2 / 0'), +] + + +def result_color_space(color_space): + if color_space == 'xyz': + return 'xyz-d65' + else: + return color_space + + +lines = [] +for color_space in COLOR_SPACES: + for permutation in PERMUTATIONS: + lines.append(' "color({} {})", "color({} {})"'.format( + color_space, permutation[0], result_color_space(color_space), permutation[1])) + + +print("[") +print(',\n'.join(lines)) +print("]") From 4d4fddd7e19ba8e6bd6bedd330d318f2c68b8070 Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Tue, 13 Dec 2022 10:54:32 +0100 Subject: [PATCH 3/9] Add color() functions to public interface. --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 510c2fe6..fb96c4e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,8 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser) pub use crate::color::{ hsl_to_rgb, hwb_to_rgb, parse_color_keyword, AbsoluteColor, AngleOrNumber, Color, - ColorComponentParser, Lab, Lch, NumberOrPercentage, Oklab, Oklch, RGBA, + ColorComponentParser, ColorFunction, Lab, Lch, NumberOrPercentage, Oklab, Oklch, + PredefinedColorSpace, RGBA, }; pub use crate::cow_rc_str::CowRcStr; pub use crate::from_bytes::{stylesheet_encoding, EncodingSupport}; From 79d11204d745c13a1e624df5c316f24375b23287 Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Fri, 16 Dec 2022 02:10:47 +0100 Subject: [PATCH 4/9] Clean up color() interface. --- src/color.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++-------- src/tests.rs | 9 ++---- 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/src/color.rs b/src/color.rs index acc71cfd..12886974 100644 --- a/src/color.rs +++ b/src/color.rs @@ -221,7 +221,7 @@ macro_rules! impl_lab_like { #[cfg(feature = "serde")] impl Serialize for $cls { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> Result where S: Serializer, { @@ -231,7 +231,7 @@ macro_rules! impl_lab_like { #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for $cls { - fn deserialize(deserializer: D) -> Result + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { @@ -309,7 +309,7 @@ macro_rules! impl_lch_like { #[cfg(feature = "serde")] impl Serialize for $cls { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> Result where S: Serializer, { @@ -319,7 +319,7 @@ macro_rules! impl_lch_like { #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for $cls { - fn deserialize(deserializer: D) -> Result + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { @@ -350,20 +350,31 @@ macro_rules! impl_lch_like { impl_lch_like!(Lch, "lch"); impl_lch_like!(Oklch, "oklch"); +/// A Predefined color space specified in: +/// https://w3c.github.io/csswg-drafts/css-color-4/#predefined #[derive(Clone, Copy, PartialEq, Debug)] pub enum PredefinedColorSpace { + /// https://w3c.github.io/csswg-drafts/css-color-4/#predefined-sRGB Srgb, + /// https://w3c.github.io/csswg-drafts/css-color-4/#predefined-sRGB-linear SrgbLinear, + /// https://w3c.github.io/csswg-drafts/css-color-4/#predefined-display-p3 DisplayP3, + /// https://w3c.github.io/csswg-drafts/css-color-4/#predefined-a98-rgb A98Rgb, + /// https://w3c.github.io/csswg-drafts/css-color-4/#predefined-prophoto-rgb ProphotoRgb, + /// https://w3c.github.io/csswg-drafts/css-color-4/#predefined-rec2020 Rec2020, + /// https://w3c.github.io/csswg-drafts/css-color-4/#predefined-xyz XyzD50, + /// https://w3c.github.io/csswg-drafts/css-color-4/#predefined-xyz XyzD65, } impl PredefinedColorSpace { - fn as_str(&self) -> &str { + /// Returns the string value of the predefined color space. + pub fn as_str(&self) -> &str { match self { PredefinedColorSpace::Srgb => "srgb", PredefinedColorSpace::SrgbLinear => "srgb-linear", @@ -377,34 +388,86 @@ impl PredefinedColorSpace { } } +impl ToCss for PredefinedColorSpace { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str(self.as_str()) + } +} + #[cfg(feature = "serde")] -impl Serialize for PredefinedColorSpace { - fn serialize(&self, serializer: S) -> Result +impl<'de> Deserialize<'de> for PredefinedColorSpace { + fn deserialize(_deserializer: D) -> Result where - S: Serializer, + D: Deserializer<'de>, { - self.as_str().serialize(serializer) + todo!() } } -impl ToCss for PredefinedColorSpace { - fn to_css(&self, dest: &mut W) -> fmt::Result +#[cfg(feature = "serde")] +impl Serialize for PredefinedColorSpace { + fn serialize(&self, serializer: S) -> Result where - W: fmt::Write, + S: Serializer, { - dest.write_str(self.as_str()) + self.as_str().serialize(serializer) } } +/// A color specified by the color() function. +/// https://w3c.github.io/csswg-drafts/css-color-4/#color-function #[derive(Clone, Copy, PartialEq, Debug)] pub struct ColorFunction { + /// The color space for this color. pub color_space: PredefinedColorSpace, + /// The first component of the color. Either red or x. pub c1: f32, + /// The second component of the color. Either green or y. pub c2: f32, + /// The third component of the color. Either blue or z. pub c3: f32, + /// The alpha component of the color. pub alpha: f32, } +impl ColorFunction { + /// Construct a new color function definition with the given color space and + /// color components. + pub fn new(color_space: PredefinedColorSpace, c1: f32, c2: f32, c3: f32, alpha: f32) -> Self { + Self { + color_space, + c1, + c2, + c3, + alpha, + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for ColorFunction { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + (self.color_space, self.c1, self.c2, self.c3, self.alpha).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for ColorFunction { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let (color_space, r, g, b, a) = Deserialize::deserialize(deserializer)?; + Ok(ColorFunction::new(color_space, r, g, b, a)) + } +} + impl ToCss for ColorFunction { fn to_css(&self, dest: &mut W) -> fmt::Result where diff --git a/src/tests.rs b/src/tests.rs index da2bb3de..8237240e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -15,8 +15,8 @@ use self::test::Bencher; use super::{ color::{rgb, rgba}, parse_important, parse_nth, parse_one_declaration, parse_one_rule, stylesheet_encoding, - AbsoluteColor, AtRuleParser, BasicParseError, BasicParseErrorKind, Color, CowRcStr, - DeclarationListParser, DeclarationParser, Delimiter, EncodingSupport, ParseError, + AbsoluteColor, AtRuleParser, BasicParseError, BasicParseErrorKind, Color, + CowRcStr, DeclarationListParser, DeclarationParser, Delimiter, EncodingSupport, ParseError, ParseErrorKind, Parser, ParserInput, ParserState, QualifiedRuleParser, RuleListParser, SourceLocation, ToCss, Token, TokenSerializationType, UnicodeRange, RGBA, }; @@ -866,7 +866,6 @@ where } } -#[cfg(feature = "serde")] impl ToJson for Color { fn to_json(&self) -> Value { match *self { @@ -879,9 +878,7 @@ impl ToJson for Color { AbsoluteColor::Lch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]), AbsoluteColor::Oklab(ref c) => json!([c.lightness, c.a, c.b, c.alpha]), AbsoluteColor::Oklch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]), - AbsoluteColor::ColorFunction(ref c) => { - json!([c.color_space, c.red, c.green, c.blue, c.alpha]) - } + AbsoluteColor::ColorFunction(ref c) => json!([c.color_space.as_str(), c.c1, c.c2, c.c3, c.alpha]), }, } } From 8e242836db4825c295f2777e821fc53035060d0d Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Fri, 20 Jan 2023 11:52:57 +0100 Subject: [PATCH 5/9] Add deserialize for predefined color spaces. --- src/color.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/color.rs b/src/color.rs index 12886974..b905a180 100644 --- a/src/color.rs +++ b/src/color.rs @@ -388,6 +388,25 @@ impl PredefinedColorSpace { } } +impl std::str::FromStr for PredefinedColorSpace { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s.to_lowercase().as_str() { + "srgb" => PredefinedColorSpace::Srgb, + "srgb-linear" => PredefinedColorSpace::SrgbLinear, + "display-p3" => PredefinedColorSpace::DisplayP3, + "a98-rgb" => PredefinedColorSpace::A98Rgb, + "prophoto-rgb" => PredefinedColorSpace::ProphotoRgb, + "rec2020" => PredefinedColorSpace::Rec2020, + "xyz-d50" => PredefinedColorSpace::XyzD50, + "xyz-d65" => PredefinedColorSpace::XyzD65, + + _ => return Err(()), + }) + } +} + impl ToCss for PredefinedColorSpace { fn to_css(&self, dest: &mut W) -> fmt::Result where @@ -399,11 +418,16 @@ impl ToCss for PredefinedColorSpace { #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for PredefinedColorSpace { - fn deserialize(_deserializer: D) -> Result + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - todo!() + use std::str::FromStr; + + let s: &str = Deserialize::deserialize(deserializer)?; + + PredefinedColorSpace::from_str(s) + .map_err(|_| serde::de::Error::custom("invalid predefined color space")) } } From 20826df169774901ccd4222ae3584d791d0926cd Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Mon, 23 Jan 2023 13:51:49 +0100 Subject: [PATCH 6/9] Address code review items. --- src/color.rs | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/color.rs b/src/color.rs index b905a180..30791a8d 100644 --- a/src/color.rs +++ b/src/color.rs @@ -4,6 +4,7 @@ use std::f32::consts::PI; use std::fmt; +use std::str::FromStr; use super::{BasicParseError, ParseError, Parser, ToCss, Token}; @@ -388,11 +389,11 @@ impl PredefinedColorSpace { } } -impl std::str::FromStr for PredefinedColorSpace { +impl FromStr for PredefinedColorSpace { type Err = (); fn from_str(s: &str) -> Result { - Ok(match s.to_lowercase().as_str() { + Ok(match_ignore_ascii_case! { s, "srgb" => PredefinedColorSpace::Srgb, "srgb-linear" => PredefinedColorSpace::SrgbLinear, "display-p3" => PredefinedColorSpace::DisplayP3, @@ -400,7 +401,7 @@ impl std::str::FromStr for PredefinedColorSpace { "prophoto-rgb" => PredefinedColorSpace::ProphotoRgb, "rec2020" => PredefinedColorSpace::Rec2020, "xyz-d50" => PredefinedColorSpace::XyzD50, - "xyz-d65" => PredefinedColorSpace::XyzD65, + "xyz" | "xyz-d65" => PredefinedColorSpace::XyzD65, _ => return Err(()), }) @@ -422,10 +423,7 @@ impl<'de> Deserialize<'de> for PredefinedColorSpace { where D: Deserializer<'de>, { - use std::str::FromStr; - let s: &str = Deserialize::deserialize(deserializer)?; - PredefinedColorSpace::from_str(s) .map_err(|_| serde::de::Error::custom("invalid predefined color space")) } @@ -477,7 +475,14 @@ impl Serialize for ColorFunction { where S: Serializer, { - (self.color_space, self.c1, self.c2, self.c3, self.alpha).serialize(serializer) + ( + self.color_space.as_str(), + self.c1, + self.c2, + self.c3, + self.alpha, + ) + .serialize(serializer) } } @@ -499,11 +504,11 @@ impl ToCss for ColorFunction { { dest.write_str("color(")?; self.color_space.to_css(dest)?; - dest.write_str(" ")?; + dest.write_char(' ')?; self.c1.to_css(dest)?; - dest.write_str(" ")?; + dest.write_char(' ')?; self.c2.to_css(dest)?; - dest.write_str(" ")?; + dest.write_char(' ')?; self.c3.to_css(dest)?; serialize_alpha(dest, self.alpha, false)?; @@ -1252,18 +1257,8 @@ where let location = arguments.current_source_location(); let color_space = arguments.try_parse(|i| { let ident = i.expect_ident()?; - Ok(match_ignore_ascii_case! { - ident, - "srgb" => PredefinedColorSpace::Srgb, - "srgb-linear" => PredefinedColorSpace::SrgbLinear, - "display-p3" => PredefinedColorSpace::DisplayP3, - "a98-rgb" => PredefinedColorSpace::A98Rgb, - "prophoto-rgb" => PredefinedColorSpace::ProphotoRgb, - "rec2020" => PredefinedColorSpace::Rec2020, - "xyz-d50" => PredefinedColorSpace::XyzD50, - "xyz" | "xyz-d65" => PredefinedColorSpace::XyzD65, - _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))) - }) + PredefinedColorSpace::from_str(ident) + .map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone()))) })?; macro_rules! parse_component { From 917f52ab3f4088abefb02a62d08f4775a8c5713a Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Wed, 25 Jan 2023 16:43:50 +0100 Subject: [PATCH 7/9] Remove color function serialization. --- src/color.rs | 50 -------------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/src/color.rs b/src/color.rs index 30791a8d..1867a709 100644 --- a/src/color.rs +++ b/src/color.rs @@ -417,28 +417,6 @@ impl ToCss for PredefinedColorSpace { } } -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for PredefinedColorSpace { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s: &str = Deserialize::deserialize(deserializer)?; - PredefinedColorSpace::from_str(s) - .map_err(|_| serde::de::Error::custom("invalid predefined color space")) - } -} - -#[cfg(feature = "serde")] -impl Serialize for PredefinedColorSpace { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.as_str().serialize(serializer) - } -} - /// A color specified by the color() function. /// https://w3c.github.io/csswg-drafts/css-color-4/#color-function #[derive(Clone, Copy, PartialEq, Debug)] @@ -469,34 +447,6 @@ impl ColorFunction { } } -#[cfg(feature = "serde")] -impl Serialize for ColorFunction { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - ( - self.color_space.as_str(), - self.c1, - self.c2, - self.c3, - self.alpha, - ) - .serialize(serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for ColorFunction { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let (color_space, r, g, b, a) = Deserialize::deserialize(deserializer)?; - Ok(ColorFunction::new(color_space, r, g, b, a)) - } -} - impl ToCss for ColorFunction { fn to_css(&self, dest: &mut W) -> fmt::Result where From ec6e33db3152e262371d26a4304404e4c3a9df8b Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Wed, 25 Jan 2023 17:26:42 +0100 Subject: [PATCH 8/9] Fix parsing of color when there are missing components. --- src/color.rs | 17 +++--- .../color4_color_function.json | 54 ------------------- .../make_color4_color_function.py | 6 --- src/tests.rs | 23 ++++++-- 4 files changed, 28 insertions(+), 72 deletions(-) diff --git a/src/color.rs b/src/color.rs index 1867a709..d311c0ae 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1205,19 +1205,18 @@ where ComponentParser: ColorComponentParser<'i>, { let location = arguments.current_source_location(); - let color_space = arguments.try_parse(|i| { - let ident = i.expect_ident()?; + + let color_space = { + let ident = arguments.expect_ident()?; PredefinedColorSpace::from_str(ident) - .map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone()))) - })?; + .map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))? + }; macro_rules! parse_component { () => {{ - if let Ok(c) = arguments.try_parse(|i| component_parser.parse_number_or_percentage(i)) { - c.unit_value() - } else { - 0.0 - } + component_parser + .parse_number_or_percentage(arguments)? + .unit_value() }}; } diff --git a/src/css-parsing-tests/color4_color_function.json b/src/css-parsing-tests/color4_color_function.json index a2464d1d..a653c653 100644 --- a/src/css-parsing-tests/color4_color_function.json +++ b/src/css-parsing-tests/color4_color_function.json @@ -12,12 +12,6 @@ "color(srgb 0 0 0 / -10%)", "color(srgb 0 0 0 / 0)", "color(srgb 0 0 0 / 110%)", "color(srgb 0 0 0)", "color(srgb 0 0 0 / 300%)", "color(srgb 0 0 0)", - "color(srgb 50% -200)", "color(srgb 0.5 -200 0)", - "color(srgb 50%)", "color(srgb 0.5 0 0)", - "color(srgb )", "color(srgb 0 0 0)", - "color(srgb 50% -200 / 0.5)", "color(srgb 0.5 -200 0 / 0.5)", - "color(srgb 50% / 0.5)", "color(srgb 0.5 0 0 / 0.5)", - "color(srgb / 0.5)", "color(srgb 0 0 0 / 0.5)", "color(srgb 200 200 200)", "color(srgb 200 200 200)", "color(srgb 200 200 200 / 200)", "color(srgb 200 200 200)", "color(srgb -200 -200 -200)", "color(srgb -200 -200 -200)", @@ -38,12 +32,6 @@ "color(srgb-linear 0 0 0 / -10%)", "color(srgb-linear 0 0 0 / 0)", "color(srgb-linear 0 0 0 / 110%)", "color(srgb-linear 0 0 0)", "color(srgb-linear 0 0 0 / 300%)", "color(srgb-linear 0 0 0)", - "color(srgb-linear 50% -200)", "color(srgb-linear 0.5 -200 0)", - "color(srgb-linear 50%)", "color(srgb-linear 0.5 0 0)", - "color(srgb-linear )", "color(srgb-linear 0 0 0)", - "color(srgb-linear 50% -200 / 0.5)", "color(srgb-linear 0.5 -200 0 / 0.5)", - "color(srgb-linear 50% / 0.5)", "color(srgb-linear 0.5 0 0 / 0.5)", - "color(srgb-linear / 0.5)", "color(srgb-linear 0 0 0 / 0.5)", "color(srgb-linear 200 200 200)", "color(srgb-linear 200 200 200)", "color(srgb-linear 200 200 200 / 200)", "color(srgb-linear 200 200 200)", "color(srgb-linear -200 -200 -200)", "color(srgb-linear -200 -200 -200)", @@ -64,12 +52,6 @@ "color(display-p3 0 0 0 / -10%)", "color(display-p3 0 0 0 / 0)", "color(display-p3 0 0 0 / 110%)", "color(display-p3 0 0 0)", "color(display-p3 0 0 0 / 300%)", "color(display-p3 0 0 0)", - "color(display-p3 50% -200)", "color(display-p3 0.5 -200 0)", - "color(display-p3 50%)", "color(display-p3 0.5 0 0)", - "color(display-p3 )", "color(display-p3 0 0 0)", - "color(display-p3 50% -200 / 0.5)", "color(display-p3 0.5 -200 0 / 0.5)", - "color(display-p3 50% / 0.5)", "color(display-p3 0.5 0 0 / 0.5)", - "color(display-p3 / 0.5)", "color(display-p3 0 0 0 / 0.5)", "color(display-p3 200 200 200)", "color(display-p3 200 200 200)", "color(display-p3 200 200 200 / 200)", "color(display-p3 200 200 200)", "color(display-p3 -200 -200 -200)", "color(display-p3 -200 -200 -200)", @@ -90,12 +72,6 @@ "color(a98-rgb 0 0 0 / -10%)", "color(a98-rgb 0 0 0 / 0)", "color(a98-rgb 0 0 0 / 110%)", "color(a98-rgb 0 0 0)", "color(a98-rgb 0 0 0 / 300%)", "color(a98-rgb 0 0 0)", - "color(a98-rgb 50% -200)", "color(a98-rgb 0.5 -200 0)", - "color(a98-rgb 50%)", "color(a98-rgb 0.5 0 0)", - "color(a98-rgb )", "color(a98-rgb 0 0 0)", - "color(a98-rgb 50% -200 / 0.5)", "color(a98-rgb 0.5 -200 0 / 0.5)", - "color(a98-rgb 50% / 0.5)", "color(a98-rgb 0.5 0 0 / 0.5)", - "color(a98-rgb / 0.5)", "color(a98-rgb 0 0 0 / 0.5)", "color(a98-rgb 200 200 200)", "color(a98-rgb 200 200 200)", "color(a98-rgb 200 200 200 / 200)", "color(a98-rgb 200 200 200)", "color(a98-rgb -200 -200 -200)", "color(a98-rgb -200 -200 -200)", @@ -116,12 +92,6 @@ "color(prophoto-rgb 0 0 0 / -10%)", "color(prophoto-rgb 0 0 0 / 0)", "color(prophoto-rgb 0 0 0 / 110%)", "color(prophoto-rgb 0 0 0)", "color(prophoto-rgb 0 0 0 / 300%)", "color(prophoto-rgb 0 0 0)", - "color(prophoto-rgb 50% -200)", "color(prophoto-rgb 0.5 -200 0)", - "color(prophoto-rgb 50%)", "color(prophoto-rgb 0.5 0 0)", - "color(prophoto-rgb )", "color(prophoto-rgb 0 0 0)", - "color(prophoto-rgb 50% -200 / 0.5)", "color(prophoto-rgb 0.5 -200 0 / 0.5)", - "color(prophoto-rgb 50% / 0.5)", "color(prophoto-rgb 0.5 0 0 / 0.5)", - "color(prophoto-rgb / 0.5)", "color(prophoto-rgb 0 0 0 / 0.5)", "color(prophoto-rgb 200 200 200)", "color(prophoto-rgb 200 200 200)", "color(prophoto-rgb 200 200 200 / 200)", "color(prophoto-rgb 200 200 200)", "color(prophoto-rgb -200 -200 -200)", "color(prophoto-rgb -200 -200 -200)", @@ -142,12 +112,6 @@ "color(rec2020 0 0 0 / -10%)", "color(rec2020 0 0 0 / 0)", "color(rec2020 0 0 0 / 110%)", "color(rec2020 0 0 0)", "color(rec2020 0 0 0 / 300%)", "color(rec2020 0 0 0)", - "color(rec2020 50% -200)", "color(rec2020 0.5 -200 0)", - "color(rec2020 50%)", "color(rec2020 0.5 0 0)", - "color(rec2020 )", "color(rec2020 0 0 0)", - "color(rec2020 50% -200 / 0.5)", "color(rec2020 0.5 -200 0 / 0.5)", - "color(rec2020 50% / 0.5)", "color(rec2020 0.5 0 0 / 0.5)", - "color(rec2020 / 0.5)", "color(rec2020 0 0 0 / 0.5)", "color(rec2020 200 200 200)", "color(rec2020 200 200 200)", "color(rec2020 200 200 200 / 200)", "color(rec2020 200 200 200)", "color(rec2020 -200 -200 -200)", "color(rec2020 -200 -200 -200)", @@ -168,12 +132,6 @@ "color(xyz 0 0 0 / -10%)", "color(xyz-d65 0 0 0 / 0)", "color(xyz 0 0 0 / 110%)", "color(xyz-d65 0 0 0)", "color(xyz 0 0 0 / 300%)", "color(xyz-d65 0 0 0)", - "color(xyz 50% -200)", "color(xyz-d65 0.5 -200 0)", - "color(xyz 50%)", "color(xyz-d65 0.5 0 0)", - "color(xyz )", "color(xyz-d65 0 0 0)", - "color(xyz 50% -200 / 0.5)", "color(xyz-d65 0.5 -200 0 / 0.5)", - "color(xyz 50% / 0.5)", "color(xyz-d65 0.5 0 0 / 0.5)", - "color(xyz / 0.5)", "color(xyz-d65 0 0 0 / 0.5)", "color(xyz 200 200 200)", "color(xyz-d65 200 200 200)", "color(xyz 200 200 200 / 200)", "color(xyz-d65 200 200 200)", "color(xyz -200 -200 -200)", "color(xyz-d65 -200 -200 -200)", @@ -194,12 +152,6 @@ "color(xyz-d50 0 0 0 / -10%)", "color(xyz-d50 0 0 0 / 0)", "color(xyz-d50 0 0 0 / 110%)", "color(xyz-d50 0 0 0)", "color(xyz-d50 0 0 0 / 300%)", "color(xyz-d50 0 0 0)", - "color(xyz-d50 50% -200)", "color(xyz-d50 0.5 -200 0)", - "color(xyz-d50 50%)", "color(xyz-d50 0.5 0 0)", - "color(xyz-d50 )", "color(xyz-d50 0 0 0)", - "color(xyz-d50 50% -200 / 0.5)", "color(xyz-d50 0.5 -200 0 / 0.5)", - "color(xyz-d50 50% / 0.5)", "color(xyz-d50 0.5 0 0 / 0.5)", - "color(xyz-d50 / 0.5)", "color(xyz-d50 0 0 0 / 0.5)", "color(xyz-d50 200 200 200)", "color(xyz-d50 200 200 200)", "color(xyz-d50 200 200 200 / 200)", "color(xyz-d50 200 200 200)", "color(xyz-d50 -200 -200 -200)", "color(xyz-d50 -200 -200 -200)", @@ -220,12 +172,6 @@ "color(xyz-d65 0 0 0 / -10%)", "color(xyz-d65 0 0 0 / 0)", "color(xyz-d65 0 0 0 / 110%)", "color(xyz-d65 0 0 0)", "color(xyz-d65 0 0 0 / 300%)", "color(xyz-d65 0 0 0)", - "color(xyz-d65 50% -200)", "color(xyz-d65 0.5 -200 0)", - "color(xyz-d65 50%)", "color(xyz-d65 0.5 0 0)", - "color(xyz-d65 )", "color(xyz-d65 0 0 0)", - "color(xyz-d65 50% -200 / 0.5)", "color(xyz-d65 0.5 -200 0 / 0.5)", - "color(xyz-d65 50% / 0.5)", "color(xyz-d65 0.5 0 0 / 0.5)", - "color(xyz-d65 / 0.5)", "color(xyz-d65 0 0 0 / 0.5)", "color(xyz-d65 200 200 200)", "color(xyz-d65 200 200 200)", "color(xyz-d65 200 200 200 / 200)", "color(xyz-d65 200 200 200)", "color(xyz-d65 -200 -200 -200)", "color(xyz-d65 -200 -200 -200)", diff --git a/src/css-parsing-tests/make_color4_color_function.py b/src/css-parsing-tests/make_color4_color_function.py index 5b8c01b0..28df857c 100644 --- a/src/css-parsing-tests/make_color4_color_function.py +++ b/src/css-parsing-tests/make_color4_color_function.py @@ -23,12 +23,6 @@ ('0 0 0 / -10%', '0 0 0 / 0'), ('0 0 0 / 110%', '0 0 0'), ('0 0 0 / 300%', '0 0 0'), - ('50% -200', '0.5 -200 0'), - ('50%', '0.5 0 0'), - ('', '0 0 0'), - ('50% -200 / 0.5', '0.5 -200 0 / 0.5'), - ('50% / 0.5', '0.5 0 0 / 0.5'), - ('/ 0.5', '0 0 0 / 0.5'), ('200 200 200', '200 200 200'), ('200 200 200 / 200', '200 200 200'), ('-200 -200 -200', '-200 -200 -200'), diff --git a/src/tests.rs b/src/tests.rs index 8237240e..c1437216 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -15,8 +15,8 @@ use self::test::Bencher; use super::{ color::{rgb, rgba}, parse_important, parse_nth, parse_one_declaration, parse_one_rule, stylesheet_encoding, - AbsoluteColor, AtRuleParser, BasicParseError, BasicParseErrorKind, Color, - CowRcStr, DeclarationListParser, DeclarationParser, Delimiter, EncodingSupport, ParseError, + AbsoluteColor, AtRuleParser, BasicParseError, BasicParseErrorKind, Color, CowRcStr, + DeclarationListParser, DeclarationParser, Delimiter, EncodingSupport, ParseError, ParseErrorKind, Parser, ParserInput, ParserState, QualifiedRuleParser, RuleListParser, SourceLocation, ToCss, Token, TokenSerializationType, UnicodeRange, RGBA, }; @@ -416,6 +416,21 @@ fn color4_color_function() { ) } +macro_rules! parse_single_color { + ($i:expr) => {{ + let input = $i; + let mut input = ParserInput::new(input); + let mut input = Parser::new(&mut input); + Color::parse(&mut input).map_err(Into::>::into) + }}; +} + +#[test] +fn color4_invalid_color_space() { + let result = parse_single_color!("color(invalid 1 1 1)"); + assert!(result.is_err()); +} + #[test] fn nth() { run_json_tests(include_str!("css-parsing-tests/An+B.json"), |input| { @@ -878,7 +893,9 @@ impl ToJson for Color { AbsoluteColor::Lch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]), AbsoluteColor::Oklab(ref c) => json!([c.lightness, c.a, c.b, c.alpha]), AbsoluteColor::Oklch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]), - AbsoluteColor::ColorFunction(ref c) => json!([c.color_space.as_str(), c.c1, c.c2, c.c3, c.alpha]), + AbsoluteColor::ColorFunction(ref c) => { + json!([c.color_space.as_str(), c.c1, c.c2, c.c3, c.alpha]) + } }, } } From 755fefff7420b918c4de59aba0a4417decf5d977 Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Wed, 25 Jan 2023 17:28:20 +0100 Subject: [PATCH 9/9] Move location into deeper scope. --- src/color.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/color.rs b/src/color.rs index d311c0ae..83c6ca7f 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1204,9 +1204,9 @@ fn parse_color_function<'i, 't, ComponentParser>( where ComponentParser: ColorComponentParser<'i>, { - let location = arguments.current_source_location(); - let color_space = { + let location = arguments.current_source_location(); + let ident = arguments.expect_ident()?; PredefinedColorSpace::from_str(ident) .map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))?