diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:37 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:37 +0000 |
commit | a90a5cba08fdf6c0ceb95101c275108a152a3aed (patch) | |
tree | 532507288f3defd7f4dcf1af49698bcb76034855 /servo/components/style/color | |
parent | Adding debian version 126.0.1-1. (diff) | |
download | firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.tar.xz firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style/color')
-rw-r--r-- | servo/components/style/color/color_function.rs | 28 | ||||
-rw-r--r-- | servo/components/style/color/component.rs | 52 | ||||
-rw-r--r-- | servo/components/style/color/mod.rs | 71 | ||||
-rw-r--r-- | servo/components/style/color/parsing.rs | 516 | ||||
-rw-r--r-- | servo/components/style/color/to_css.rs | 8 |
5 files changed, 406 insertions, 269 deletions
diff --git a/servo/components/style/color/color_function.rs b/servo/components/style/color/color_function.rs index 2edb4fff3b..426256a2e9 100644 --- a/servo/components/style/color/color_function.rs +++ b/servo/components/style/color/color_function.rs @@ -4,7 +4,7 @@ //! Output of parsing a color function, e.g. rgb(..), hsl(..), color(..) -use crate::values::normalize; +use crate::{color::ColorFlags, values::normalize}; use cssparser::color::{PredefinedColorSpace, OPAQUE}; use super::{ @@ -30,6 +30,7 @@ pub enum ColorFunction { ColorComponent<NumberOrPercentage>, // saturation ColorComponent<NumberOrPercentage>, // lightness ColorComponent<NumberOrPercentage>, // alpha + bool, // is_legacy_syntax ), /// <https://drafts.csswg.org/css-color-4/#the-hwb-notation> Hwb( @@ -37,6 +38,7 @@ pub enum ColorFunction { ColorComponent<NumberOrPercentage>, // whiteness ColorComponent<NumberOrPercentage>, // blackness ColorComponent<NumberOrPercentage>, // alpha + bool, // is_legacy_syntax ), /// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch> Lab( @@ -104,31 +106,43 @@ impl ColorFunction { AbsoluteColor::srgb_legacy(r, g, b, alpha!(alpha).unwrap_or(0.0)) }, - ColorFunction::Hsl(h, s, l, alpha) => { + ColorFunction::Hsl(h, s, l, alpha, is_legacy_syntax) => { // Percent reference range for S and L: 0% = 0.0, 100% = 100.0 const LIGHTNESS_RANGE: f32 = 100.0; const SATURATION_RANGE: f32 = 100.0; - AbsoluteColor::new( + let mut result = AbsoluteColor::new( ColorSpace::Hsl, value!(h).map(|angle| normalize_hue(angle.degrees())), value!(s).map(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)), value!(l).map(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)), alpha!(alpha), - ) + ); + + if *is_legacy_syntax { + result.flags.insert(ColorFlags::IS_LEGACY_SRGB); + } + + result }, - ColorFunction::Hwb(h, w, b, alpha) => { + ColorFunction::Hwb(h, w, b, alpha, is_legacy_syntax) => { // Percent reference range for W and B: 0% = 0.0, 100% = 100.0 const WHITENESS_RANGE: f32 = 100.0; const BLACKNESS_RANGE: f32 = 100.0; - AbsoluteColor::new( + let mut result = AbsoluteColor::new( ColorSpace::Hwb, value!(h).map(|angle| normalize_hue(angle.degrees())), value!(w).map(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)), value!(b).map(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)), alpha!(alpha), - ) + ); + + if *is_legacy_syntax { + result.flags.insert(ColorFlags::IS_LEGACY_SRGB); + } + + result }, ColorFunction::Lab(l, a, b, alpha) => { // for L: 0% = 0.0, 100% = 100.0 diff --git a/servo/components/style/color/component.rs b/servo/components/style/color/component.rs index 5f9d8a137e..7edebb6267 100644 --- a/servo/components/style/color/component.rs +++ b/servo/components/style/color/component.rs @@ -4,6 +4,10 @@ //! Parse/serialize and resolve a single color component. +use super::{ + parsing::{rcs_enabled, ChannelKeyword}, + AbsoluteColor, +}; use crate::{ parser::ParserContext, values::{ @@ -60,6 +64,12 @@ impl<ValueType> ColorComponent<ValueType> { /// An utility trait that allows the construction of [ColorComponent] /// `ValueType`'s after parsing a color component. pub trait ColorComponentType: Sized { + // TODO(tlouw): This function should be named according to the rules in the spec + // stating that all the values coming from color components are + // numbers and that each has their own rules dependeing on types. + /// Construct a new component from a single value. + fn from_value(value: f32) -> Self; + /// Return the [CalcUnits] flags that the impl can handle. fn units() -> CalcUnits; @@ -77,6 +87,7 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> { context: &ParserContext, input: &mut Parser<'i, 't>, allow_none: bool, + origin_color: Option<&AbsoluteColor>, ) -> Result<Self, ParseError<'i>> { let location = input.current_source_location(); @@ -84,11 +95,48 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> { Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { Ok(ColorComponent::None) }, + ref t @ Token::Ident(ref ident) if origin_color.is_some() => { + if let Ok(channel_keyword) = ChannelKeyword::from_ident(ident) { + if let Ok(value) = origin_color + .unwrap() + .get_component_by_channel_keyword(channel_keyword) + { + Ok(Self::Value(ValueType::from_value(value.unwrap_or(0.0)))) + } else { + Err(location.new_unexpected_token_error(t.clone())) + } + } else { + Err(location.new_unexpected_token_error(t.clone())) + } + }, Token::Function(ref name) => { let function = SpecifiedCalcNode::math_function(context, name, location)?; - let node = SpecifiedCalcNode::parse(context, input, function, ValueType::units())?; + let units = if rcs_enabled() { + ValueType::units() | CalcUnits::COLOR_COMPONENT + } else { + ValueType::units() + }; + let node = SpecifiedCalcNode::parse(context, input, function, units)?; - let Ok(resolved_leaf) = node.resolve() else { + let Ok(resolved_leaf) = node.resolve_map(|leaf| { + // + Ok(match leaf { + SpecifiedLeaf::ColorComponent(channel_keyword) => { + if let Some(origin_color) = origin_color { + if let Ok(value) = + origin_color.get_component_by_channel_keyword(*channel_keyword) + { + SpecifiedLeaf::Number(value.unwrap_or(0.0)) + } else { + return Err(()); + } + } else { + return Err(()); + } + }, + l => l.clone(), + }) + }) else { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); }; diff --git a/servo/components/style/color/mod.rs b/servo/components/style/color/mod.rs index f7e661cddf..7e427faa84 100644 --- a/servo/components/style/color/mod.rs +++ b/servo/components/style/color/mod.rs @@ -15,6 +15,7 @@ pub mod mix; pub mod parsing; mod to_css; +use self::parsing::ChannelKeyword; use component::ColorComponent; use cssparser::color::PredefinedColorSpace; @@ -449,6 +450,76 @@ impl AbsoluteColor { } } + /// Return the value of a component by its channel keyword. + pub fn get_component_by_channel_keyword( + &self, + channel_keyword: ChannelKeyword, + ) -> Result<Option<f32>, ()> { + if channel_keyword == ChannelKeyword::Alpha { + return Ok(self.alpha()); + } + + Ok(match self.color_space { + ColorSpace::Srgb => { + if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) { + match channel_keyword { + ChannelKeyword::R => self.c0().map(|v| v * 255.0), + ChannelKeyword::G => self.c1().map(|v| v * 255.0), + ChannelKeyword::B => self.c2().map(|v| v * 255.0), + _ => return Err(()), + } + } else { + match channel_keyword { + ChannelKeyword::R => self.c0(), + ChannelKeyword::G => self.c1(), + ChannelKeyword::B => self.c2(), + _ => return Err(()), + } + } + }, + ColorSpace::Hsl => match channel_keyword { + ChannelKeyword::H => self.c0(), + ChannelKeyword::S => self.c1(), + ChannelKeyword::L => self.c2(), + _ => return Err(()), + }, + ColorSpace::Hwb => match channel_keyword { + ChannelKeyword::H => self.c0(), + ChannelKeyword::W => self.c1(), + ChannelKeyword::B => self.c2(), + _ => return Err(()), + }, + ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword { + ChannelKeyword::L => self.c0(), + ChannelKeyword::A => self.c1(), + ChannelKeyword::B => self.c2(), + _ => return Err(()), + }, + ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword { + ChannelKeyword::L => self.c0(), + ChannelKeyword::C => self.c1(), + ChannelKeyword::H => self.c2(), + _ => return Err(()), + }, + ColorSpace::SrgbLinear | + ColorSpace::DisplayP3 | + ColorSpace::A98Rgb | + ColorSpace::ProphotoRgb | + ColorSpace::Rec2020 => match channel_keyword { + ChannelKeyword::R => self.c0(), + ChannelKeyword::G => self.c1(), + ChannelKeyword::B => self.c2(), + _ => return Err(()), + }, + ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword { + ChannelKeyword::X => self.c0(), + ChannelKeyword::Y => self.c1(), + ChannelKeyword::Z => self.c2(), + _ => return Err(()), + }, + }) + } + /// Convert this color to the specified color space. pub fn to_color_space(&self, color_space: ColorSpace) -> Self { use ColorSpace::*; diff --git a/servo/components/style/color/parsing.rs b/servo/components/style/color/parsing.rs index 68bcee6c56..0c8fb2dbaf 100644 --- a/servo/components/style/color/parsing.rs +++ b/servo/components/style/color/parsing.rs @@ -4,17 +4,15 @@ #![deny(missing_docs)] -//! Fairly complete css-color implementation. -//! Relative colors, color-mix, system colors, and other such things require better calc() support -//! and integration. +//! Parsing for CSS colors. use super::{ color_function::ColorFunction, component::{ColorComponent, ColorComponentType}, - AbsoluteColor, + AbsoluteColor, ColorFlags, ColorSpace, }; use crate::{ - parser::ParserContext, + parser::{Parse, ParserContext}, values::{ generics::calc::CalcUnits, specified::{ @@ -27,7 +25,6 @@ use cssparser::{ color::{clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE}, match_ignore_ascii_case, CowRcStr, Parser, Token, }; -use std::str::FromStr; use style_traits::{ParseError, StyleParseErrorKind}; /// Returns true if the relative color syntax pref is enabled. @@ -36,11 +33,35 @@ pub fn rcs_enabled() -> bool { static_prefs::pref!("layout.css.relative-color-syntax.enabled") } -impl From<u8> for ColorComponent<u8> { - #[inline] - fn from(value: u8) -> Self { - ColorComponent::Value(value) - } +/// Represents a channel keyword inside a color. +#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, PartialOrd, ToCss, ToShmem)] +pub enum ChannelKeyword { + /// alpha + Alpha, + /// a + A, + /// b, blackness, blue + B, + /// chroma + C, + /// green + G, + /// hue + H, + /// lightness + L, + /// red + R, + /// saturation + S, + /// whiteness + W, + /// x + X, + /// y + Y, + /// z + Z, } /// Return the named color with the given name. @@ -65,7 +86,7 @@ pub fn parse_color_keyword(ident: &str) -> Result<SpecifiedColor, ()> { /// Parse a CSS color using the specified [`ColorParser`] and return a new color /// value on success. pub fn parse_color_with<'i, 't>( - color_parser: &ColorParser<'_, '_>, + context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<SpecifiedColor, ParseError<'i>> { let location = input.current_source_location(); @@ -80,7 +101,7 @@ pub fn parse_color_with<'i, 't>( let name = name.clone(); return input.parse_nested_block(|arguments| { Ok(SpecifiedColor::from_absolute_color( - parse_color_function(color_parser, name, arguments)?.resolve_to_absolute(), + parse_color_function(context, name, arguments)?.resolve_to_absolute(), )) }); }, @@ -92,19 +113,26 @@ pub fn parse_color_with<'i, 't>( /// Parse one of the color functions: rgba(), lab(), color(), etc. #[inline] fn parse_color_function<'i, 't>( - color_parser: &ColorParser<'_, '_>, + context: &ParserContext, name: CowRcStr<'i>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { + let origin_color = parse_origin_color(context, arguments)?; + + let component_parser = ComponentParser { + context, + origin_color, + }; + let color = match_ignore_ascii_case! { &name, - "rgb" | "rgba" => parse_rgb(color_parser, arguments), - "hsl" | "hsla" => parse_hsl(color_parser, arguments), - "hwb" => parse_hwb(color_parser, arguments), - "lab" => parse_lab_like(color_parser, arguments, ColorFunction::Lab), - "lch" => parse_lch_like(color_parser, arguments, ColorFunction::Lch), - "oklab" => parse_lab_like(color_parser, arguments, ColorFunction::Oklab), - "oklch" => parse_lch_like(color_parser, arguments, ColorFunction::Oklch), - "color" => parse_color_with_color_space(color_parser, arguments), + "rgb" | "rgba" => parse_rgb(&component_parser, arguments), + "hsl" | "hsla" => parse_hsl(&component_parser, arguments), + "hwb" => parse_hwb(&component_parser, arguments), + "lab" => parse_lab_like(&component_parser, arguments, ColorSpace::Lab, ColorFunction::Lab), + "lch" => parse_lch_like(&component_parser, arguments, ColorSpace::Lch, ColorFunction::Lch), + "oklab" => parse_lab_like(&component_parser, arguments, ColorSpace::Oklab, ColorFunction::Oklab), + "oklch" => parse_lch_like(&component_parser, arguments, ColorSpace::Oklch, ColorFunction::Oklch), + "color" =>parse_color_with_color_space(&component_parser, arguments), _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))), }?; @@ -113,34 +141,6 @@ fn parse_color_function<'i, 't>( Ok(color) } -fn parse_legacy_alpha<'i, 't>( - color_parser: &ColorParser<'_, '_>, - arguments: &mut Parser<'i, 't>, -) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { - if !arguments.is_exhausted() { - arguments.expect_comma()?; - color_parser.parse_number_or_percentage(arguments, false) - } else { - Ok(ColorComponent::Value(NumberOrPercentage::Number { - value: OPAQUE, - })) - } -} - -fn parse_modern_alpha<'i, 't>( - color_parser: &ColorParser<'_, '_>, - arguments: &mut Parser<'i, 't>, -) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { - if !arguments.is_exhausted() { - arguments.expect_delim('/')?; - color_parser.parse_number_or_percentage(arguments, true) - } else { - Ok(ColorComponent::Value(NumberOrPercentage::Number { - value: OPAQUE, - })) - } -} - impl ColorComponent<NumberOrPercentage> { /// Return true if the component contains a percentage. pub fn is_percentage(&self) -> Result<bool, ()> { @@ -153,9 +153,9 @@ impl ColorComponent<NumberOrPercentage> { /// Parse the relative color syntax "from" syntax `from <color>`. fn parse_origin_color<'i, 't>( - color_parser: &ColorParser<'_, '_>, + context: &ParserContext, arguments: &mut Parser<'i, 't>, -) -> Result<Option<SpecifiedColor>, ParseError<'i>> { +) -> Result<Option<AbsoluteColor>, ParseError<'i>> { if !rcs_enabled() { return Ok(None); } @@ -169,72 +169,112 @@ fn parse_origin_color<'i, 't>( return Ok(None); } + let location = arguments.current_source_location(); + // We still fail if we can't parse the origin color. - parse_color_with(color_parser, arguments).map(|color| Some(color)) + let origin_color = SpecifiedColor::parse(context, arguments)?; + + // Right now we only handle absolute colors. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1890972 + let Some(computed) = origin_color.to_computed_color(None) else { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + }; + + Ok(Some(computed.resolve_to_absolute(&AbsoluteColor::BLACK))) } #[inline] fn parse_rgb<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { - let origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser.origin_color.map(|c| { + let mut c = c.to_color_space(ColorSpace::Srgb); + c.flags.insert(ColorFlags::IS_LEGACY_SRGB); + c + }), + }; let location = arguments.current_source_location(); - let maybe_red = color_parser.parse_number_or_percentage(arguments, true)?; + let maybe_red = component_parser.parse_number_or_percentage(arguments, true)?; // If the first component is not "none" and is followed by a comma, then we // are parsing the legacy syntax. Legacy syntax also doesn't support an // origin color. - let is_legacy_syntax = origin_color.is_none() && + let is_legacy_syntax = component_parser.origin_color.is_none() && !maybe_red.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok(); - let (red, green, blue, alpha) = if is_legacy_syntax { + Ok(if is_legacy_syntax { let Ok(is_percentage) = maybe_red.is_percentage() else { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); }; let (red, green, blue) = if is_percentage { let red = maybe_red.map_value(|v| clamp_unit_f32(v.to_number(1.0))); - let green = color_parser + let green = component_parser .parse_percentage(arguments, false)? .map_value(clamp_unit_f32); arguments.expect_comma()?; - let blue = color_parser + let blue = component_parser .parse_percentage(arguments, false)? .map_value(clamp_unit_f32); (red, green, blue) } else { let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0))); - let green = color_parser + let green = component_parser .parse_number(arguments, false)? .map_value(clamp_floor_256_f32); arguments.expect_comma()?; - let blue = color_parser + let blue = component_parser .parse_number(arguments, false)? .map_value(clamp_floor_256_f32); (red, green, blue) }; - let alpha = parse_legacy_alpha(color_parser, arguments)?; + let alpha = component_parser.parse_legacy_alpha(arguments)?; - (red, green, blue, alpha) + ColorFunction::Rgb(red, green, blue, alpha) } else { - let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0))); - let green = color_parser - .parse_number_or_percentage(arguments, true)? - .map_value(|v| clamp_floor_256_f32(v.to_number(255.0))); - let blue = color_parser - .parse_number_or_percentage(arguments, true)? - .map_value(|v| clamp_floor_256_f32(v.to_number(255.0))); - - let alpha = parse_modern_alpha(color_parser, arguments)?; + let green = component_parser.parse_number_or_percentage(arguments, true)?; + let blue = component_parser.parse_number_or_percentage(arguments, true)?; + + let alpha = component_parser.parse_modern_alpha(arguments)?; + + // When using the relative color syntax (having an origin color), the + // resulting color is always in the modern syntax. + if component_parser.origin_color.is_some() { + fn adjust(v: NumberOrPercentage) -> NumberOrPercentage { + if let NumberOrPercentage::Number { value } = v { + NumberOrPercentage::Number { + value: value / 255.0, + } + } else { + v + } + } + + ColorFunction::Color( + PredefinedColorSpace::Srgb, + maybe_red.map_value(adjust), + green.map_value(adjust), + blue.map_value(adjust), + alpha, + ) + } else { + fn clamp(v: NumberOrPercentage) -> u8 { + clamp_floor_256_f32(v.to_number(255.0)) + } - (red, green, blue, alpha) - }; + let red = maybe_red.map_value(clamp); + let green = green.map_value(clamp); + let blue = blue.map_value(clamp); - Ok(ColorFunction::Rgb(red, green, blue, alpha)) + ColorFunction::Rgb(red, green, blue, alpha) + } + }) } /// Parses hsl syntax. @@ -242,43 +282,48 @@ fn parse_rgb<'i, 't>( /// <https://drafts.csswg.org/css-color/#the-hsl-notation> #[inline] fn parse_hsl<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { - let origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(ColorSpace::Hsl)), + }; - let hue = color_parser.parse_number_or_angle(arguments, true)?; + let hue = component_parser.parse_number_or_angle(arguments, true)?; // If the hue is not "none" and is followed by a comma, then we are parsing // the legacy syntax. Legacy syntax also doesn't support an origin color. - let is_legacy_syntax = origin_color.is_none() && + let is_legacy_syntax = component_parser.origin_color.is_none() && !hue.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok(); let (saturation, lightness, alpha) = if is_legacy_syntax { - let saturation = color_parser + let saturation = component_parser .parse_percentage(arguments, false)? .map_value(|unit_value| NumberOrPercentage::Percentage { unit_value }); arguments.expect_comma()?; - let lightness = color_parser + let lightness = component_parser .parse_percentage(arguments, false)? .map_value(|unit_value| NumberOrPercentage::Percentage { unit_value }); - ( - saturation, - lightness, - parse_legacy_alpha(color_parser, arguments)?, - ) + let alpha = component_parser.parse_legacy_alpha(arguments)?; + (saturation, lightness, alpha) } else { - let saturation = color_parser.parse_number_or_percentage(arguments, true)?; - let lightness = color_parser.parse_number_or_percentage(arguments, true)?; - ( - saturation, - lightness, - parse_modern_alpha(color_parser, arguments)?, - ) + let saturation = component_parser.parse_number_or_percentage(arguments, true)?; + let lightness = component_parser.parse_number_or_percentage(arguments, true)?; + let alpha = component_parser.parse_modern_alpha(arguments)?; + (saturation, lightness, alpha) }; - Ok(ColorFunction::Hsl(hue, saturation, lightness, alpha)) + Ok(ColorFunction::Hsl( + hue, + saturation, + lightness, + alpha, + component_parser.origin_color.is_none(), + )) } /// Parses hwb syntax. @@ -286,20 +331,29 @@ fn parse_hsl<'i, 't>( /// <https://drafts.csswg.org/css-color/#the-hbw-notation> #[inline] fn parse_hwb<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { - let _origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(ColorSpace::Hwb)), + }; - let (hue, whiteness, blackness, alpha) = parse_components( - color_parser, - arguments, - ColorParser::parse_number_or_angle, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - )?; + let hue = component_parser.parse_number_or_angle(arguments, true)?; + let whiteness = component_parser.parse_number_or_percentage(arguments, true)?; + let blackness = component_parser.parse_number_or_percentage(arguments, true)?; - Ok(ColorFunction::Hwb(hue, whiteness, blackness, alpha)) + let alpha = component_parser.parse_modern_alpha(arguments)?; + + Ok(ColorFunction::Hwb( + hue, + whiteness, + blackness, + alpha, + component_parser.origin_color.is_none(), + )) } type IntoLabFn<Output> = fn( @@ -311,19 +365,23 @@ type IntoLabFn<Output> = fn( #[inline] fn parse_lab_like<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, + color_space: ColorSpace, into_color: IntoLabFn<ColorFunction>, ) -> Result<ColorFunction, ParseError<'i>> { - let _origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(color_space)), + }; - let (lightness, a, b, alpha) = parse_components( - color_parser, - arguments, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - )?; + let lightness = component_parser.parse_number_or_percentage(arguments, true)?; + let a = component_parser.parse_number_or_percentage(arguments, true)?; + let b = component_parser.parse_number_or_percentage(arguments, true)?; + + let alpha = component_parser.parse_modern_alpha(arguments)?; Ok(into_color(lightness, a, b, alpha)) } @@ -337,19 +395,23 @@ type IntoLchFn<Output> = fn( #[inline] fn parse_lch_like<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, + color_space: ColorSpace, into_color: IntoLchFn<ColorFunction>, ) -> Result<ColorFunction, ParseError<'i>> { - let _origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(color_space)), + }; - let (lightness, chroma, hue, alpha) = parse_components( - color_parser, - arguments, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_angle, - )?; + let lightness = component_parser.parse_number_or_percentage(arguments, true)?; + let chroma = component_parser.parse_number_or_percentage(arguments, true)?; + let hue = component_parser.parse_number_or_angle(arguments, true)?; + + let alpha = component_parser.parse_modern_alpha(arguments)?; Ok(into_color(lightness, chroma, hue, alpha)) } @@ -357,72 +419,24 @@ fn parse_lch_like<'i, 't>( /// Parse the color() function. #[inline] fn parse_color_with_color_space<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { - let _origin_color = parse_origin_color(color_parser, arguments)?; - - 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())))? + let color_space = PredefinedColorSpace::parse(arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(ColorSpace::from(color_space))), }; - let (c1, c2, c3, alpha) = parse_components( - color_parser, - arguments, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - )?; + let c1 = component_parser.parse_number_or_percentage(arguments, true)?; + let c2 = component_parser.parse_number_or_percentage(arguments, true)?; + let c3 = component_parser.parse_number_or_percentage(arguments, true)?; - Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha)) -} + let alpha = component_parser.parse_modern_alpha(arguments)?; -type ComponentParseResult<'i, R1, R2, R3> = Result< - ( - ColorComponent<R1>, - ColorComponent<R2>, - ColorComponent<R3>, - ColorComponent<NumberOrPercentage>, - ), - ParseError<'i>, ->; - -/// Parse the color components and alpha with the modern [color-4] syntax. -pub fn parse_components<'a, 'b: 'a, 'i, 't, F1, F2, F3, R1, R2, R3>( - color_parser: &ColorParser<'a, 'b>, - input: &mut Parser<'i, 't>, - f1: F1, - f2: F2, - f3: F3, -) -> ComponentParseResult<'i, R1, R2, R3> -where - F1: FnOnce( - &ColorParser<'a, 'b>, - &mut Parser<'i, 't>, - bool, - ) -> Result<ColorComponent<R1>, ParseError<'i>>, - F2: FnOnce( - &ColorParser<'a, 'b>, - &mut Parser<'i, 't>, - bool, - ) -> Result<ColorComponent<R2>, ParseError<'i>>, - F3: FnOnce( - &ColorParser<'a, 'b>, - &mut Parser<'i, 't>, - bool, - ) -> Result<ColorComponent<R3>, ParseError<'i>>, -{ - let r1 = f1(color_parser, input, true)?; - let r2 = f2(color_parser, input, true)?; - let r3 = f3(color_parser, input, true)?; - - let alpha = parse_modern_alpha(color_parser, input)?; - - Ok((r1, r2, r3, alpha)) + Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha)) } /// Either a number or a percentage. @@ -453,6 +467,10 @@ impl NumberOrPercentage { } impl ColorComponentType for NumberOrPercentage { + fn from_value(value: f32) -> Self { + Self::Number { value } + } + fn units() -> CalcUnits { CalcUnits::PERCENTAGE } @@ -503,6 +521,10 @@ impl NumberOrAngle { } impl ColorComponentType for NumberOrAngle { + fn from_value(value: f32) -> Self { + Self::Number { value } + } + fn units() -> CalcUnits { CalcUnits::ANGLE } @@ -538,6 +560,10 @@ impl ColorComponentType for NumberOrAngle { /// The raw f32 here is for <number>. impl ColorComponentType for f32 { + fn from_value(value: f32) -> Self { + value + } + fn units() -> CalcUnits { CalcUnits::empty() } @@ -560,19 +586,29 @@ impl ColorComponentType for f32 { } /// Used to parse the components of a color. -pub struct ColorParser<'a, 'b: 'a> { +pub struct ComponentParser<'a, 'b: 'a> { /// Parser context used for parsing the colors. pub context: &'a ParserContext<'b>, + /// The origin color that will be used to resolve relative components. + pub origin_color: Option<AbsoluteColor>, } -impl<'a, 'b: 'a> ColorParser<'a, 'b> { +impl<'a, 'b: 'a> ComponentParser<'a, 'b> { + /// Create a new [ColorParser] with the given context. + pub fn new(context: &'a ParserContext<'b>) -> Self { + Self { + context, + origin_color: None, + } + } + /// Parse an `<number>` or `<angle>` value. fn parse_number_or_angle<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> { - ColorComponent::parse(self.context, input, allow_none) + ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref()) } /// Parse a `<percentage>` value. @@ -587,7 +623,12 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> { // doesn't have any more overhead than just parsing a percentage on its // own. Ok( - match ColorComponent::<NumberOrPercentage>::parse(self.context, input, allow_none)? { + match ColorComponent::<NumberOrPercentage>::parse( + self.context, + input, + allow_none, + self.origin_color.as_ref(), + )? { ColorComponent::None => ColorComponent::None, ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) => { ColorComponent::Value(unit_value) @@ -603,7 +644,7 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> { input: &mut Parser<'i, 't>, allow_none: bool, ) -> Result<ColorComponent<f32>, ParseError<'i>> { - ColorComponent::parse(self.context, input, allow_none) + ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref()) } /// Parse a `<number>` or `<percentage>` value. @@ -612,77 +653,34 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> { input: &mut Parser<'i, 't>, allow_none: bool, ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { - ColorComponent::parse(self.context, input, allow_none) + ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref()) } -} -/// This trait is used by the [`ColorParser`] to construct colors of any type. -pub trait FromParsedColor { - /// Construct a new color from the CSS `currentcolor` keyword. - fn from_current_color() -> Self; - - /// Construct a new color from red, green, blue and alpha components. - fn from_rgba( - red: ColorComponent<u8>, - green: ColorComponent<u8>, - blue: ColorComponent<u8>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from hue, saturation, lightness and alpha components. - fn from_hsl( - hue: ColorComponent<NumberOrAngle>, - saturation: ColorComponent<NumberOrPercentage>, - lightness: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from hue, blackness, whiteness and alpha components. - fn from_hwb( - hue: ColorComponent<NumberOrAngle>, - whiteness: ColorComponent<NumberOrPercentage>, - blackness: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from the `lab` notation. - fn from_lab( - lightness: ColorComponent<NumberOrPercentage>, - a: ColorComponent<NumberOrPercentage>, - b: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from the `lch` notation. - fn from_lch( - lightness: ColorComponent<NumberOrPercentage>, - chroma: ColorComponent<NumberOrPercentage>, - hue: ColorComponent<NumberOrAngle>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from the `oklab` notation. - fn from_oklab( - lightness: ColorComponent<NumberOrPercentage>, - a: ColorComponent<NumberOrPercentage>, - b: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from the `oklch` notation. - fn from_oklch( - lightness: ColorComponent<NumberOrPercentage>, - chroma: ColorComponent<NumberOrPercentage>, - hue: ColorComponent<NumberOrAngle>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color with a predefined color space. - fn from_color_function( - color_space: PredefinedColorSpace, - c1: ColorComponent<NumberOrPercentage>, - c2: ColorComponent<NumberOrPercentage>, - c3: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; + fn parse_legacy_alpha<'i, 't>( + &self, + arguments: &mut Parser<'i, 't>, + ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { + if !arguments.is_exhausted() { + arguments.expect_comma()?; + self.parse_number_or_percentage(arguments, false) + } else { + Ok(ColorComponent::Value(NumberOrPercentage::Number { + value: OPAQUE, + })) + } + } + + fn parse_modern_alpha<'i, 't>( + &self, + arguments: &mut Parser<'i, 't>, + ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { + if !arguments.is_exhausted() { + arguments.expect_delim('/')?; + self.parse_number_or_percentage(arguments, true) + } else { + Ok(ColorComponent::Value(NumberOrPercentage::Number { + value: self.origin_color.map(|c| c.alpha).unwrap_or(OPAQUE), + })) + } + } } diff --git a/servo/components/style/color/to_css.rs b/servo/components/style/color/to_css.rs index 350d8386f1..aba809759a 100644 --- a/servo/components/style/color/to_css.rs +++ b/servo/components/style/color/to_css.rs @@ -60,7 +60,13 @@ impl ToCss for AbsoluteColor { dest.write_char(')') }, - ColorSpace::Hsl | ColorSpace::Hwb => self.into_srgb_legacy().to_css(dest), + ColorSpace::Hsl | ColorSpace::Hwb => { + if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) { + self.into_srgb_legacy().to_css(dest) + } else { + self.to_color_space(ColorSpace::Srgb).to_css(dest) + } + }, ColorSpace::Oklab | ColorSpace::Lab | ColorSpace::Oklch | ColorSpace::Lch => { if let ColorSpace::Oklab | ColorSpace::Oklch = self.color_space { dest.write_str("ok")?; |