From 4559546fbbc51de8ff06a8b5e95cb5b8007e60f3 Mon Sep 17 00:00:00 2001 From: Tiaan Louw Date: Fri, 20 Jan 2023 10:55:51 +0000 Subject: [PATCH] style: Add lab(), lch(), oklab(), oklch() to specified colors Use new changes from cssparser and use the new lab/lch/oklab/oklch color formats. Introduced a new color type AbsoluteColor. It represents any kind of color that has absolute numerical values. It is also tied to a color space and therefore can be trivially converted to another color space. Differential Revision: https://phabricator.services.mozilla.com/D163579 --- components/selectors/Cargo.toml | 2 +- components/style/Cargo.toml | 2 +- components/style/gecko/values.rs | 18 +- components/style/properties/cascade.rs | 10 +- .../longhands/inherited_text.mako.rs | 2 +- .../properties/longhands/inherited_ui.mako.rs | 2 - .../stylesheets/font_palette_values_rule.rs | 11 +- components/style/values/animated/color.rs | 184 ++++++++- components/style/values/computed/color.rs | 6 +- components/style/values/specified/color.rs | 360 +++++++++++++++--- components/style_traits/Cargo.toml | 2 +- components/style_traits/values.rs | 3 +- 12 files changed, 512 insertions(+), 90 deletions(-) diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml index e0ee246dcea..b1afce74581 100644 --- a/components/selectors/Cargo.toml +++ b/components/selectors/Cargo.toml @@ -21,7 +21,7 @@ shmem = ["dep:to_shmem", "dep:to_shmem_derive"] [dependencies] bitflags = "1.0" -cssparser = "0.29" +cssparser = "0.30" derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign"] } fxhash = "0.2" log = "0.4" diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 48f022a356b..a70c379761d 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -41,7 +41,7 @@ arrayvec = "0.7" atomic_refcell = "0.1" bitflags = "1.0" byteorder = "1.0" -cssparser = "0.29" +cssparser = "0.30" derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "from"] } encoding_rs = { version = "0.8", optional = true } euclid = "0.22" diff --git a/components/style/gecko/values.rs b/components/style/gecko/values.rs index a755556e850..23e9b6e7502 100644 --- a/components/style/gecko/values.rs +++ b/components/style/gecko/values.rs @@ -18,20 +18,18 @@ use std::cmp::max; /// Convert a given RGBA value to `nscolor`. pub fn convert_rgba_to_nscolor(rgba: &RGBA) -> u32 { - ((rgba.alpha as u32) << 24) | - ((rgba.blue as u32) << 16) | - ((rgba.green as u32) << 8) | - (rgba.red as u32) + u32::from_le_bytes([ + rgba.red, + rgba.green, + rgba.blue, + (rgba.alpha * 255.0).round() as u8, + ]) } /// Convert a given `nscolor` to a Servo RGBA value. pub fn convert_nscolor_to_rgba(color: u32) -> RGBA { - RGBA::new( - (color & 0xff) as u8, - (color >> 8 & 0xff) as u8, - (color >> 16 & 0xff) as u8, - (color >> 24 & 0xff) as u8, - ) + let [r, g, b, a] = color.to_le_bytes(); + RGBA::new(r, g, b, a as f32 / 255.0) } /// Round `width` down to the nearest device pixel, but any non-zero value that diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index ce8973f4939..95d8f309934 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -433,9 +433,11 @@ fn tweak_when_ignoring_colors( return; } - fn alpha_channel(color: &Color, context: &computed::Context) -> u8 { + fn alpha_channel(color: &Color, context: &computed::Context) -> f32 { // We assume here currentColor is opaque. - let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255)); + let color = color + .to_computed_value(context) + .into_rgba(RGBA::new(0, 0, 0, 1.0)); color.alpha } @@ -458,7 +460,7 @@ fn tweak_when_ignoring_colors( // otherwise, this is needed to preserve semi-transparent // backgrounds. let alpha = alpha_channel(color, context); - if alpha == 0 { + if alpha == 0.0 { return; } let mut color = context.builder.device.default_background_color(); @@ -474,7 +476,7 @@ fn tweak_when_ignoring_colors( // If the inherited color would be transparent, but we would // override this with a non-transparent color, then override it with // the default color. Otherwise just let it inherit through. - if context.builder.get_parent_inherited_text().clone_color().alpha == 0 { + if context.builder.get_parent_inherited_text().clone_color().alpha == 0.0 { let color = context.builder.device.default_color(); declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( specified::ColorPropertyValue(color.into()), diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs index 5d097579b54..9a6eb4f410e 100644 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ b/components/style/properties/longhands/inherited_text.mako.rs @@ -9,7 +9,7 @@ ${helpers.predefined_type( "color", "ColorPropertyValue", - "::cssparser::RGBA::new(0, 0, 0, 255)", + "::cssparser::RGBA::new(0, 0, 0, 1.0)", engines="gecko servo", animation_value_type="AnimatedRGBA", ignored_when_colors_disabled="True", diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs index dd559f4e354..9c1218118e0 100644 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ b/components/style/properties/longhands/inherited_ui.mako.rs @@ -78,7 +78,6 @@ ${helpers.predefined_type( engines="gecko", spec="https://drafts.csswg.org/css-ui/#caret-color", animation_value_type="CaretColor", - boxed=True, ignored_when_colors_disabled=True, )} @@ -90,7 +89,6 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-ui-4/#widget-accent", gecko_pref="layout.css.accent-color.enabled", animation_value_type="ColorOrAuto", - boxed=True, ignored_when_colors_disabled=True, has_effect_on_gecko_scrollbars=False, )} diff --git a/components/style/stylesheets/font_palette_values_rule.rs b/components/style/stylesheets/font_palette_values_rule.rs index 970bd481f9f..3bfae5e6b1b 100644 --- a/components/style/stylesheets/font_palette_values_rule.rs +++ b/components/style/stylesheets/font_palette_values_rule.rs @@ -47,8 +47,8 @@ impl Parse for FontPaletteOverrideColor { let location = input.current_source_location(); let color = SpecifiedColor::parse(context, input)?; // Only absolute colors are accepted here. - if let SpecifiedColor::Numeric { parsed: _, authored: _ } = color { - Ok(FontPaletteOverrideColor{ index, color }) + if let SpecifiedColor::Absolute { .. } = color { + Ok(FontPaletteOverrideColor { index, color }) } else { Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -183,11 +183,10 @@ impl FontPaletteValuesRule { } } for c in &self.override_colors { - if let SpecifiedColor::Numeric { parsed, authored: _ } = &c.color { + if let SpecifiedColor::Absolute(ref absolute) = c.color { + let rgba = absolute.color.to_rgba(); unsafe { - Gecko_SetFontPaletteOverride(palette_values, - c.index.0.value(), - *parsed); + Gecko_SetFontPaletteOverride(palette_values, c.index.0.value(), rgba); } } } diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs index ac3a2ad2d3e..ddd6bd66783 100644 --- a/components/style/values/animated/color.rs +++ b/components/style/values/animated/color.rs @@ -168,6 +168,13 @@ impl Color { rgba.alpha *= alpha_multiplier; } + // FIXME: In rare cases we end up with 0.999995 in the alpha channel, + // so we reduce the precision to avoid serializing to + // rgba(?, ?, ?, 1). This is not ideal, so we should look into + // ways to avoid it. Maybe pre-multiply all color components and + // then divide after calculations? + rgba.alpha = (rgba.alpha * 1000.0).round() / 1000.0; + rgba } } @@ -424,29 +431,56 @@ struct XYZD50A { impl_lerp!(XYZD50A, None); +#[allow(missing_docs)] #[derive(Clone, Copy, Debug)] #[repr(C)] -struct LABA { - lightness: f32, - a: f32, - b: f32, - alpha: f32, +pub struct LABA { + pub lightness: f32, + pub a: f32, + pub b: f32, + pub alpha: f32, } impl_lerp!(LABA, None); /// An animated LCHA colour. +#[allow(missing_docs)] #[derive(Clone, Copy, Debug)] #[repr(C)] -struct LCHA { - lightness: f32, - chroma: f32, - hue: f32, - alpha: f32, +pub struct LCHA { + pub lightness: f32, + pub chroma: f32, + pub hue: f32, + pub alpha: f32, } impl_lerp!(LCHA, Some(2)); +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct OKLABA { + pub lightness: f32, + pub a: f32, + pub b: f32, + pub alpha: f32, +} + +impl_lerp!(OKLABA, None); + +/// An animated OKLCHA colour. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct OKLCHA { + pub lightness: f32, + pub chroma: f32, + pub hue: f32, + pub alpha: f32, +} + +impl_lerp!(OKLCHA, Some(2)); + /// An animated hwb() color. #[derive(Clone, Copy, Debug)] #[repr(C)] @@ -604,6 +638,7 @@ impl From for XYZD50A { -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0., 0., 0., 0., 1., ); + let d50 = BRADFORD.transform_vector3d(Vector3D::new(d65.x, d65.y, d65.z)); Self { x: d50.x, @@ -747,6 +782,22 @@ impl From for LCHA { } } +impl From for OKLCHA { + /// Convert an OKLAB color to OKLCH as specified in [1]. + /// + /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code + fn from(oklaba: OKLABA) -> Self { + let hue = oklaba.b.atan2(oklaba.a) * DEG_PER_RAD; + let chroma = (oklaba.a * oklaba.a + oklaba.b * oklaba.b).sqrt(); + OKLCHA { + lightness: oklaba.lightness, + chroma, + hue, + alpha: oklaba.alpha, + } + } +} + impl From for LABA { /// Convert a LCH color to LAB as specified in [1]. /// @@ -764,6 +815,23 @@ impl From for LABA { } } +impl From for OKLABA { + /// Convert a OKLCH color to OKLAB as specified in [1]. + /// + /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code + fn from(oklcha: OKLCHA) -> Self { + let hue_radians = oklcha.hue * RAD_PER_DEG; + let a = oklcha.chroma * hue_radians.cos(); + let b = oklcha.chroma * hue_radians.sin(); + OKLABA { + lightness: oklcha.lightness, + a, + b, + alpha: oklcha.alpha, + } + } +} + impl From for XYZD50A { /// Convert a CIELAB color to XYZ as specified in [1] and [2]. /// @@ -804,6 +872,78 @@ impl From for XYZD50A { } } +impl From for OKLABA { + fn from(xyza: XYZD65A) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code + #[cfg_attr(rustfmt, rustfmt_skip)] + const XYZ_TO_LMS: Transform3D = Transform3D::new( + 0.8190224432164319, 0.0329836671980271, 0.048177199566046255, 0., + 0.3619062562801221, 0.9292868468965546, 0.26423952494422764, 0., + -0.12887378261216414, 0.03614466816999844, 0.6335478258136937, 0., + 0., 0., 0., 1., + ); + + #[cfg_attr(rustfmt, rustfmt_skip)] + const LMS_TO_OKLAB: Transform3D = Transform3D::new( + 0.2104542553, 1.9779984951, 0.0259040371, 0., + 0.7936177850, -2.4285922050, 0.7827717662, 0., + -0.0040720468, 0.4505937099, -0.8086757660, 0., + 0., 0., 0., 1., + ); + + let lms = XYZ_TO_LMS.transform_vector3d(Vector3D::new(xyza.x, xyza.y, xyza.z)); + let lab = LMS_TO_OKLAB.transform_vector3d(Vector3D::new( + lms.x.cbrt(), + lms.y.cbrt(), + lms.z.cbrt(), + )); + + Self { + lightness: lab.x, + a: lab.y, + b: lab.z, + alpha: xyza.alpha, + } + } +} + +impl From for XYZD65A { + fn from(oklaba: OKLABA) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code + // Given OKLab, convert to XYZ relative to D65 + #[cfg_attr(rustfmt, rustfmt_skip)] + const LMS_TO_XYZ: Transform3D = Transform3D::new( + 1.2268798733741557, -0.04057576262431372, -0.07637294974672142, 0., + -0.5578149965554813, 1.1122868293970594, -0.4214933239627914, 0., + 0.28139105017721583, -0.07171106666151701, 1.5869240244272418, 0., + 0., 0., 0., 1., + ); + + #[cfg_attr(rustfmt, rustfmt_skip)] + const OKLAB_TO_LMS: Transform3D = Transform3D::new( + 0.99999999845051981432, 1.0000000088817607767, 1.0000000546724109177, 0., + 0.39633779217376785678, -0.1055613423236563494, -0.089484182094965759684, 0., + 0.21580375806075880339, -0.063854174771705903402, -1.2914855378640917399, 0., + 0., 0., 0., 1., + ); + + let lms = + OKLAB_TO_LMS.transform_vector3d(Vector3D::new(oklaba.lightness, oklaba.a, oklaba.b)); + let xyz = LMS_TO_XYZ.transform_vector3d(Vector3D::new( + lms.x.powf(3.0), + lms.y.powf(3.0), + lms.z.powf(3.0), + )); + + Self { + x: xyz.x, + y: xyz.y, + z: xyz.z, + alpha: oklaba.alpha, + } + } +} + impl From for RGBA { fn from(d50: XYZD50A) -> Self { Self::from(XYZD65A::from(d50)) @@ -828,6 +968,18 @@ impl From for RGBA { } } +impl From for RGBA { + fn from(oklaba: OKLABA) -> Self { + Self::from(XYZD65A::from(oklaba)) + } +} + +impl From for OKLABA { + fn from(rgba: RGBA) -> Self { + Self::from(XYZD65A::from(rgba)) + } +} + impl From for LCHA { fn from(rgba: RGBA) -> Self { Self::from(LABA::from(rgba)) @@ -839,3 +991,15 @@ impl From for RGBA { Self::from(LABA::from(lcha)) } } + +impl From for RGBA { + fn from(oklcha: OKLCHA) -> Self { + Self::from(OKLABA::from(oklcha)) + } +} + +impl From for OKLCHA { + fn from(rgba: RGBA) -> Self { + Self::from(OKLABA::from(rgba)) + } +} diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index dd15ec41d3d..89266f3c6e3 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -30,7 +30,7 @@ impl ToCss for Color { { match *self { Self::Numeric(ref c) => c.to_css(dest), - Self::CurrentColor => CSSParserColor::CurrentColor.to_css(dest), + Self::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest), Self::ColorMix(ref m) => m.to_css(dest), } } @@ -44,12 +44,12 @@ impl Color { /// Returns opaque black. pub fn black() -> Color { - Color::rgba(RGBA::new(0, 0, 0, 255)) + Color::rgba(RGBA::new(0, 0, 0, 1.0)) } /// Returns opaque white. pub fn white() -> Color { - Color::rgba(RGBA::new(255, 255, 255, 255)) + Color::rgba(RGBA::new(255, 255, 255, 1.0)) } /// Combine this complex color with the given foreground color into diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 681c6d8add2..c78a502a133 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -98,18 +98,291 @@ impl ColorMix { } } +/// A color space representation in the CSS specification. +/// +/// https://w3c.github.io/csswg-drafts/css-color-4/#color-type +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)] +#[repr(u8)] +pub enum ColorSpace { + /// A color specified in the Lab color format, e.g. + /// "lab(29.2345% 39.3825 20.0664)". + /// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors + Lab, + /// A color specified in the Lch color format, e.g. + /// "lch(29.2345% 44.2 27)". + /// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors + Lch, + /// A color specified in the Oklab color format, e.g. + /// "oklab(40.101% 0.1147 0.0453)". + /// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors + Oklab, + /// A color specified in the Oklch color format, e.g. + /// "oklch(40.101% 0.12332 21.555)". + /// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors + Oklch, + /// A color specified with the color(..) function and the "srgb" color + /// space, e.g. "color(srgb 0.691 0.139 0.259)". + Srgb, + /// A color specified with the color(..) function and the "srgb-linear" + /// color space, e.g. "color(srgb-linear 0.435 0.017 0.055)". + SrgbLinear, + /// A color specified with the color(..) function and the "display-p3" + /// color space, e.g. "color(display-p3 0.84 0.19 0.72)". + DisplayP3, + /// A color specified with the color(..) function and the "a98-rgb" color + /// space, e.g. "color(a98-rgb 0.44091 0.49971 0.37408)". + A98Rgb, + /// A color specified with the color(..) function and the "prophoto-rgb" + /// color space, e.g. "color(prophoto-rgb 0.36589 0.41717 0.31333)". + ProphotoRgb, + /// A color specified with the color(..) function and the "rec2020" color + /// space, e.g. "color(rec2020 0.42210 0.47580 0.35605)". + Rec2020, + /// A color specified with the color(..) function and the "xyz-d50" color + /// space, e.g. "color(xyz-d50 0.2005 0.14089 0.4472)". + XyzD50, + /// A color specified with the color(..) function and the "xyz-d65" or "xyz" + /// color space, e.g. "color(xyz-d65 0.21661 0.14602 0.59452)". + XyzD65, +} + +bitflags! { + #[derive(Default, MallocSizeOf, ToShmem)] + #[repr(C)] + struct SerializationFlags : u8 { + const AS_COLOR_FUNCTION = 0x01; + } +} + +/// The 3 components that make up a color. (Does not include the alpha component) +#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] +struct ColorComponents(f32, f32, f32); + +/// An absolutely specified color, using either rgb(), rgba(), lab(), lch(), +/// oklab(), oklch() or color(). +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] +#[repr(C)] +pub struct AbsoluteColor { + components: ColorComponents, + alpha: f32, + color_space: ColorSpace, + flags: SerializationFlags, +} + +macro_rules! color_components_as { + ($c:expr, $t:ty) => {{ + // This macro is not an inline function, because we can't use the + // generic type ($t) in a constant expression as per: + // https://github.com/rust-lang/rust/issues/76560 + const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>()); + const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>()); + const_assert!(std::mem::size_of::() >= std::mem::size_of::<$t>()); + const_assert_eq!( + std::mem::align_of::(), + std::mem::align_of::<$t>() + ); + + std::mem::transmute::<&ColorComponents, &$t>(&$c.components) + }}; +} + +impl AbsoluteColor { + /// Create a new [AbsoluteColor] with the given [ColorSpace] and components. + pub fn new(color_space: ColorSpace, c1: f32, c2: f32, c3: f32, alpha: f32) -> Self { + Self { + components: ColorComponents(c1, c2, c3), + alpha, + color_space, + flags: SerializationFlags::empty(), + } + } + + /// Convenience function to create a color in the sRGB color space. + pub fn from_rgba(rgba: RGBA) -> Self { + let red = rgba.red as f32 / 255.0; + let green = rgba.green as f32 / 255.0; + let blue = rgba.blue as f32 / 255.0; + + Self::new(ColorSpace::Srgb, red, green, blue, rgba.alpha) + } + + /// Return the alpha component. + #[inline] + pub fn alpha(&self) -> f32 { + self.alpha + } + + /// Convert the color to sRGB color space and return it in the RGBA struct. + pub fn to_rgba(&self) -> RGBA { + let rgba = self.to_color_space(ColorSpace::Srgb); + + let red = (rgba.components.0 * 255.0).round() as u8; + let green = (rgba.components.1 * 255.0).round() as u8; + let blue = (rgba.components.2 * 255.0).round() as u8; + + RGBA::new(red, green, blue, rgba.alpha) + } + + /// Convert this color to the specified color space. + pub fn to_color_space(&self, color_space: ColorSpace) -> Self { + use crate::values::animated::color::{AnimatedRGBA, LABA, LCHA, OKLABA, OKLCHA}; + use ColorSpace::*; + + if self.color_space == color_space { + return self.clone(); + } + + match (self.color_space, color_space) { + (Lab, Srgb) => { + let laba = unsafe { color_components_as!(self, LABA) }; + let rgba = AnimatedRGBA::from(*laba); + Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha) + }, + + (Oklab, Srgb) => { + let oklaba: &OKLABA = unsafe { color_components_as!(self, OKLABA) }; + let rgba = AnimatedRGBA::from(*oklaba); + Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha) + }, + + (Lch, Srgb) => { + let lcha: &LCHA = unsafe { color_components_as!(self, LCHA) }; + let rgba = AnimatedRGBA::from(*lcha); + Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha) + }, + + (Oklch, Srgb) => { + let oklcha: &OKLCHA = unsafe { color_components_as!(self, OKLCHA) }; + let rgba = AnimatedRGBA::from(*oklcha); + Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha) + }, + + _ => { + // Conversion to other color spaces is not implemented yet. + log::warn!( + "Can not convert from {:?} to {:?}!", + self.color_space, + color_space + ); + Self::from_rgba(RGBA::new(0, 0, 0, 1.0)) + }, + } + } +} + +impl From for AbsoluteColor { + fn from(f: cssparser::AbsoluteColor) -> Self { + match f { + cssparser::AbsoluteColor::Rgba(rgba) => Self::from_rgba(rgba), + + cssparser::AbsoluteColor::Lab(lab) => { + Self::new(ColorSpace::Lab, lab.lightness, lab.a, lab.b, lab.alpha) + }, + + cssparser::AbsoluteColor::Lch(lch) => Self::new( + ColorSpace::Lch, + lch.lightness, + lch.chroma, + lch.hue, + lch.alpha, + ), + + cssparser::AbsoluteColor::Oklab(oklab) => Self::new( + ColorSpace::Oklab, + oklab.lightness, + oklab.a, + oklab.b, + oklab.alpha, + ), + + cssparser::AbsoluteColor::Oklch(oklch) => Self::new( + ColorSpace::Oklch, + oklch.lightness, + oklch.chroma, + oklch.hue, + oklch.alpha, + ), + } + } +} + +impl ToCss for AbsoluteColor { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + match self.color_space { + ColorSpace::Srgb if !self.flags.contains(SerializationFlags::AS_COLOR_FUNCTION) => { + cssparser::ToCss::to_css( + &cssparser::RGBA::from_floats( + self.components.0, + self.components.1, + self.components.2, + self.alpha(), + ), + dest, + ) + }, + ColorSpace::Lab => cssparser::ToCss::to_css( + unsafe { color_components_as!(self, cssparser::Lab) }, + dest, + ), + ColorSpace::Lch => cssparser::ToCss::to_css( + unsafe { color_components_as!(self, cssparser::Lch) }, + dest, + ), + ColorSpace::Oklab => cssparser::ToCss::to_css( + unsafe { color_components_as!(self, cssparser::Oklab) }, + dest, + ), + ColorSpace::Oklch => cssparser::ToCss::to_css( + unsafe { color_components_as!(self, cssparser::Oklch) }, + dest, + ), + // Other color spaces are not implemented yet. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1128204 + ColorSpace::Srgb | + ColorSpace::SrgbLinear | + ColorSpace::DisplayP3 | + ColorSpace::A98Rgb | + ColorSpace::ProphotoRgb | + ColorSpace::Rec2020 | + ColorSpace::XyzD50 | + ColorSpace::XyzD65 => todo!(), + } + } +} + +/// Container holding an absolute color and the text specified by an author. +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] +pub struct Absolute { + /// The specified color. + pub color: AbsoluteColor, + /// Authored representation. + pub authored: Option>, +} + +impl ToCss for Absolute { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if let Some(ref authored) = self.authored { + dest.write_str(authored) + } else { + self.color.to_css(dest) + } + } +} + /// Specified color value #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum Color { /// The 'currentColor' keyword CurrentColor, - /// A specific RGBA color - Numeric { - /// Parsed RGBA color - parsed: RGBA, - /// Authored representation - authored: Option>, - }, + /// An absolute color. + /// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-function + Absolute(Box), /// A system color. #[cfg(feature = "gecko")] System(SystemColor), @@ -477,16 +750,24 @@ impl Color { let authored = input.expect_ident_cloned().ok(); input.reset(&start); authored - } + }, }; let compontent_parser = ColorComponentParser(&*context); match input.try_parse(|i| CSSParserColor::parse_with(&compontent_parser, i)) { Ok(value) => Ok(match value { CSSParserColor::CurrentColor => Color::CurrentColor, - CSSParserColor::RGBA(rgba) => Color::Numeric { - parsed: rgba, - authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()), + CSSParserColor::Absolute(absolute) => { + let enabled = matches!(absolute, cssparser::AbsoluteColor::Rgba(_)) || + static_prefs::pref!("layout.css.more_color_4.enabled"); + if !enabled { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + Color::Absolute(Box::new(Absolute { + color: absolute.into(), + authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()), + })) }, }), Err(e) => { @@ -497,7 +778,8 @@ impl Color { } } - if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored)) { + if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored)) + { return Ok(Color::ColorMix(Box::new(mix))); } @@ -515,7 +797,9 @@ impl Color { /// Returns whether a given color is valid for authors. pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool { - input.parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No)).is_ok() + input + .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No)) + .is_ok() } /// Tries to parse a color and compute it with a given device. @@ -526,9 +810,8 @@ impl Color { ) -> Option { use crate::error_reporting::ContextualParseError; let start = input.position(); - let result = input.parse_entirely(|input| { - Self::parse_internal(context, input, PreserveAuthored::No) - }); + let result = input + .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No)); let specified = match result { Ok(s) => s, @@ -545,14 +828,11 @@ impl Color { // default and not available on OffscreenCanvas anyways... if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind { let location = e.location.clone(); - let error = ContextualParseError::UnsupportedValue( - input.slice_from(start), - e, - ); + let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e); context.log_css_error(location, error); } return None; - } + }, }; match device { @@ -572,14 +852,8 @@ impl ToCss for Color { W: Write, { match *self { - Color::CurrentColor => CSSParserColor::CurrentColor.to_css(dest), - Color::Numeric { - authored: Some(ref authored), - .. - } => dest.write_str(authored), - Color::Numeric { - parsed: ref rgba, .. - } => rgba.to_css(dest), + Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest), + Color::Absolute(ref absolute) => absolute.to_css(dest), Color::ColorMix(ref mix) => mix.to_css(dest), #[cfg(feature = "gecko")] Color::System(system) => system.to_css(dest), @@ -589,18 +863,6 @@ impl ToCss for Color { } } -/// A wrapper of cssparser::Color::parse_hash. -/// -/// That function should never return CurrentColor, so it makes no sense to -/// handle a cssparser::Color here. This should really be done in cssparser -/// directly rather than here. -fn parse_hash_color(value: &[u8]) -> Result { - CSSParserColor::parse_hash(value).map(|color| match color { - CSSParserColor::RGBA(rgba) => rgba, - CSSParserColor::CurrentColor => unreachable!("parse_hash should never return currentcolor"), - }) -} - impl Color { /// Returns whether this color is allowed in forced-colors mode. pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool { @@ -610,7 +872,7 @@ impl Color { Color::CurrentColor => true, #[cfg(feature = "gecko")] Color::System(..) => true, - Color::Numeric { ref parsed, .. } => allow_transparent && parsed.alpha == 0, + Color::Absolute(ref absolute) => allow_transparent && absolute.color.alpha() == 0.0, Color::ColorMix(ref mix) => { mix.left.honored_in_forced_colors_mode(allow_transparent) && mix.right.honored_in_forced_colors_mode(allow_transparent) @@ -631,13 +893,13 @@ impl Color { Color::rgba(RGBA::transparent()) } - /// Returns a numeric RGBA color value. + /// Returns an absolute RGBA color value. #[inline] pub fn rgba(rgba: RGBA) -> Self { - Color::Numeric { - parsed: rgba, + Color::Absolute(Box::new(Absolute { + color: AbsoluteColor::from_rgba(rgba), authored: None, - } + })) } /// Parse a color, with quirks. @@ -677,7 +939,7 @@ impl Color { if ident.len() != 3 && ident.len() != 6 { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } - return parse_hash_color(ident.as_bytes()).map_err(|()| { + return RGBA::parse_hash(ident.as_bytes()).map_err(|()| { location.new_custom_error(StyleParseErrorKind::UnspecifiedError) }); }, @@ -722,7 +984,7 @@ impl Color { .unwrap(); } debug_assert_eq!(written, 6); - parse_hash_color(&serialization) + RGBA::parse_hash(&serialization) .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } @@ -735,7 +997,7 @@ impl Color { pub fn to_computed_color(&self, context: Option<&Context>) -> Option { Some(match *self { Color::CurrentColor => ComputedColor::CurrentColor, - Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed), + Color::Absolute(ref absolute) => ComputedColor::Numeric(absolute.color.to_rgba()), Color::ColorMix(ref mix) => { use crate::values::computed::percentage::Percentage; diff --git a/components/style_traits/Cargo.toml b/components/style_traits/Cargo.toml index 8a135354cf8..6f5d5e6ed2c 100644 --- a/components/style_traits/Cargo.toml +++ b/components/style_traits/Cargo.toml @@ -17,7 +17,7 @@ gecko = [] [dependencies] app_units = "0.7" bitflags = "1.0" -cssparser = "0.29" +cssparser = "0.30" euclid = "0.22" lazy_static = "1" malloc_size_of = { path = "../malloc_size_of" } diff --git a/components/style_traits/values.rs b/components/style_traits/values.rs index 4cfcd131493..3ce97a06057 100644 --- a/components/style_traits/values.rs +++ b/components/style_traits/values.rs @@ -501,9 +501,8 @@ impl_to_css_for_predefined_type!(i8); impl_to_css_for_predefined_type!(i32); impl_to_css_for_predefined_type!(u16); impl_to_css_for_predefined_type!(u32); -impl_to_css_for_predefined_type!(::cssparser::Token<'a>); impl_to_css_for_predefined_type!(::cssparser::RGBA); -impl_to_css_for_predefined_type!(::cssparser::Color); +impl_to_css_for_predefined_type!(::cssparser::Token<'a>); impl_to_css_for_predefined_type!(::cssparser::UnicodeRange); /// Define an enum type with unit variants that each correspond to a CSS keyword.