diff --git a/src/color.rs b/src/color.rs index b94b4aad..83c6ca7f 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}; @@ -221,7 +222,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 +232,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 +310,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 +320,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,6 +351,122 @@ 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 { + /// Returns the string value of the predefined color space. + pub 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", + } + } +} + +impl FromStr for PredefinedColorSpace { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match_ignore_ascii_case! { s, + "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(()), + }) + } +} + +impl ToCss for PredefinedColorSpace { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str(self.as_str()) + } +} + +/// 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, + } + } +} + +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_char(' ')?; + self.c1.to_css(dest)?; + dest.write_char(' ')?; + self.c2.to_css(dest)?; + dest.write_char(' ')?; + self.c3.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 +487,8 @@ pub enum AbsoluteColor { /// Specifies an Oklab color by Oklab Lightness, Chroma, and hue using /// the OKLCH cylindrical coordinate model. Oklch(Oklch), + /// Specifies a color in a predefined color space. + ColorFunction(ColorFunction), } impl AbsoluteColor { @@ -381,6 +500,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 +516,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 +692,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 +906,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 +948,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 +1195,44 @@ 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 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())))? + }; + + macro_rules! parse_component { + () => {{ + component_parser + .parse_number_or_percentage(arguments)? + .unit_value() + }}; + } + + 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, + c1, + c2, + c3, + 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..a653c653 --- /dev/null +++ b/src/css-parsing-tests/color4_color_function.json @@ -0,0 +1,182 @@ +[ + "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 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 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 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 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 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 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 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 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 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..28df857c --- /dev/null +++ b/src/css-parsing-tests/make_color4_color_function.py @@ -0,0 +1,52 @@ +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'), + ('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("]") 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}; diff --git a/src/tests.rs b/src/tests.rs index b4d25626..c1437216 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -404,6 +404,33 @@ 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) + }, + ) +} + +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| { @@ -866,6 +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]) + } }, } }