diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:50 +0000 |
commit | def92d1b8e9d373e2f6f27c366d578d97d8960c6 (patch) | |
tree | 2ef34b9ad8bb9a9220e05d60352558b15f513894 /servo/components/style/color | |
parent | Adding debian version 125.0.3-1. (diff) | |
download | firefox-def92d1b8e9d373e2f6f27c366d578d97d8960c6.tar.xz firefox-def92d1b8e9d373e2f6f27c366d578d97d8960c6.zip |
Merging upstream version 126.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 | 198 | ||||
-rw-r--r-- | servo/components/style/color/component.rs | 58 | ||||
-rw-r--r-- | servo/components/style/color/mix.rs | 3 | ||||
-rw-r--r-- | servo/components/style/color/mod.rs | 6 | ||||
-rw-r--r-- | servo/components/style/color/parsing.rs | 416 |
5 files changed, 529 insertions, 152 deletions
diff --git a/servo/components/style/color/color_function.rs b/servo/components/style/color/color_function.rs new file mode 100644 index 0000000000..2edb4fff3b --- /dev/null +++ b/servo/components/style/color/color_function.rs @@ -0,0 +1,198 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Output of parsing a color function, e.g. rgb(..), hsl(..), color(..) + +use crate::values::normalize; +use cssparser::color::{PredefinedColorSpace, OPAQUE}; + +use super::{ + component::ColorComponent, + convert::normalize_hue, + parsing::{NumberOrAngle, NumberOrPercentage}, + AbsoluteColor, ColorSpace, +}; + +/// Represents a specified color function. +#[derive(Debug)] +pub enum ColorFunction { + /// <https://drafts.csswg.org/css-color-4/#rgb-functions> + Rgb( + ColorComponent<u8>, // red + ColorComponent<u8>, // green + ColorComponent<u8>, // blue + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#the-hsl-notation> + Hsl( + ColorComponent<NumberOrAngle>, // hue + ColorComponent<NumberOrPercentage>, // saturation + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#the-hwb-notation> + Hwb( + ColorComponent<NumberOrAngle>, // hue + ColorComponent<NumberOrPercentage>, // whiteness + ColorComponent<NumberOrPercentage>, // blackness + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch> + Lab( + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // a + ColorComponent<NumberOrPercentage>, // b + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch> + Lch( + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // chroma + ColorComponent<NumberOrAngle>, // hue + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch> + Oklab( + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // a + ColorComponent<NumberOrPercentage>, // b + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch> + Oklch( + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // chroma + ColorComponent<NumberOrAngle>, // hue + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#color-function> + Color( + PredefinedColorSpace, + ColorComponent<NumberOrPercentage>, // red / x + ColorComponent<NumberOrPercentage>, // green / y + ColorComponent<NumberOrPercentage>, // blue / z + ColorComponent<NumberOrPercentage>, // alpha + ), +} + +impl ColorFunction { + /// Try to resolve the color function to an [`AbsoluteColor`] that does not + /// contain any variables (currentcolor, color components, etc.). + pub fn resolve_to_absolute(&self) -> AbsoluteColor { + macro_rules! value { + ($v:expr) => {{ + match $v { + ColorComponent::None => None, + // value should be Copy. + ColorComponent::Value(value) => Some(*value), + } + }}; + } + + macro_rules! alpha { + ($alpha:expr) => {{ + value!($alpha).map(|value| normalize(value.to_number(1.0)).clamp(0.0, OPAQUE)) + }}; + } + + match self { + ColorFunction::Rgb(r, g, b, alpha) => { + let r = value!(r).unwrap_or(0); + let g = value!(g).unwrap_or(0); + let b = value!(b).unwrap_or(0); + + AbsoluteColor::srgb_legacy(r, g, b, alpha!(alpha).unwrap_or(0.0)) + }, + ColorFunction::Hsl(h, s, l, alpha) => { + // 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( + 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), + ) + }, + ColorFunction::Hwb(h, w, b, alpha) => { + // 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( + 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), + ) + }, + ColorFunction::Lab(l, a, b, alpha) => { + // for L: 0% = 0.0, 100% = 100.0 + // for a and b: -100% = -125, 100% = 125 + const LIGHTNESS_RANGE: f32 = 100.0; + const A_B_RANGE: f32 = 125.0; + + AbsoluteColor::new( + ColorSpace::Lab, + value!(l).map(|l| l.to_number(LIGHTNESS_RANGE)), + value!(a).map(|a| a.to_number(A_B_RANGE)), + value!(b).map(|b| b.to_number(A_B_RANGE)), + alpha!(alpha), + ) + }, + ColorFunction::Lch(l, c, h, alpha) => { + // for L: 0% = 0.0, 100% = 100.0 + // for C: 0% = 0, 100% = 150 + const LIGHTNESS_RANGE: f32 = 100.0; + const CHROMA_RANGE: f32 = 150.0; + + AbsoluteColor::new( + ColorSpace::Lch, + value!(l).map(|l| l.to_number(LIGHTNESS_RANGE)), + value!(c).map(|c| c.to_number(CHROMA_RANGE)), + value!(h).map(|angle| normalize_hue(angle.degrees())), + alpha!(alpha), + ) + }, + ColorFunction::Oklab(l, a, b, alpha) => { + // for L: 0% = 0.0, 100% = 1.0 + // for a and b: -100% = -0.4, 100% = 0.4 + const LIGHTNESS_RANGE: f32 = 1.0; + const A_B_RANGE: f32 = 0.4; + + AbsoluteColor::new( + ColorSpace::Oklab, + value!(l).map(|l| l.to_number(LIGHTNESS_RANGE)), + value!(a).map(|a| a.to_number(A_B_RANGE)), + value!(b).map(|b| b.to_number(A_B_RANGE)), + alpha!(alpha), + ) + }, + ColorFunction::Oklch(l, c, h, alpha) => { + // for L: 0% = 0.0, 100% = 1.0 + // for C: 0% = 0.0 100% = 0.4 + const LIGHTNESS_RANGE: f32 = 1.0; + const CHROMA_RANGE: f32 = 0.4; + + AbsoluteColor::new( + ColorSpace::Oklch, + value!(l).map(|l| l.to_number(LIGHTNESS_RANGE)), + value!(c).map(|c| c.to_number(CHROMA_RANGE)), + value!(h).map(|angle| normalize_hue(angle.degrees())), + alpha!(alpha), + ) + }, + ColorFunction::Color(color_space, r, g, b, alpha) => AbsoluteColor::new( + (*color_space).into(), + value!(r).map(|c| c.to_number(1.0)), + value!(g).map(|c| c.to_number(1.0)), + value!(b).map(|c| c.to_number(1.0)), + alpha!(alpha), + ), + } + } +} diff --git a/servo/components/style/color/component.rs b/servo/components/style/color/component.rs index 9f101a460c..5f9d8a137e 100644 --- a/servo/components/style/color/component.rs +++ b/servo/components/style/color/component.rs @@ -4,8 +4,18 @@ //! Parse/serialize and resolve a single color component. +use crate::{ + parser::ParserContext, + values::{ + generics::calc::CalcUnits, + specified::calc::{CalcNode as SpecifiedCalcNode, Leaf as SpecifiedLeaf}, + }, +}; +use cssparser::{Parser, Token}; +use style_traits::{ParseError, StyleParseErrorKind}; + /// A single color component. -#[derive(Clone, MallocSizeOf, PartialEq, ToShmem)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum ColorComponent<ValueType> { /// The "none" keyword. None, @@ -46,3 +56,49 @@ impl<ValueType> ColorComponent<ValueType> { } } } + +/// An utility trait that allows the construction of [ColorComponent] +/// `ValueType`'s after parsing a color component. +pub trait ColorComponentType: Sized { + /// Return the [CalcUnits] flags that the impl can handle. + fn units() -> CalcUnits; + + /// Try to create a new component from the given token. + fn try_from_token(token: &Token) -> Result<Self, ()>; + + /// Try to create a new component from the given [CalcNodeLeaf] that was + /// resolved from a [CalcNode]. + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result<Self, ()>; +} + +impl<ValueType: ColorComponentType> ColorComponent<ValueType> { + /// Parse a single [ColorComponent]. + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_none: bool, + ) -> Result<Self, ParseError<'i>> { + let location = input.current_source_location(); + + match *input.next()? { + Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { + Ok(ColorComponent::None) + }, + Token::Function(ref name) => { + let function = SpecifiedCalcNode::math_function(context, name, location)?; + let node = SpecifiedCalcNode::parse(context, input, function, ValueType::units())?; + + let Ok(resolved_leaf) = node.resolve() else { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + }; + + ValueType::try_from_leaf(&resolved_leaf) + .map(Self::Value) + .map_err(|_| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + }, + ref t => ValueType::try_from_token(t) + .map(Self::Value) + .map_err(|_| location.new_unexpected_token_error(t.clone())), + } + } +} diff --git a/servo/components/style/color/mix.rs b/servo/components/style/color/mix.rs index bcc4628575..3b3970e1e0 100644 --- a/servo/components/style/color/mix.rs +++ b/servo/components/style/color/mix.rs @@ -402,7 +402,8 @@ fn adjust_hue(left: &mut f32, right: &mut f32, hue_interpolation: HueInterpolati // https://drafts.csswg.org/css-color/#longer HueInterpolationMethod::Longer => { let delta = *right - *left; - if 0. < delta && delta < 180. { + // In the specific case of delta == 0 we need to use Decreasing + if 0. <= delta && delta < 180. { *left += 360.; } else if -180. < delta && delta <= 0. { *right += 360.; diff --git a/servo/components/style/color/mod.rs b/servo/components/style/color/mod.rs index 6f35daaa8a..f7e661cddf 100644 --- a/servo/components/style/color/mod.rs +++ b/servo/components/style/color/mod.rs @@ -4,9 +4,13 @@ //! Color support functions. -pub mod component; +/// cbindgen:ignore +mod color_function; + /// cbindgen:ignore pub mod convert; + +pub mod component; pub mod mix; pub mod parsing; mod to_css; diff --git a/servo/components/style/color/parsing.rs b/servo/components/style/color/parsing.rs index 1ae32c4ad7..68bcee6c56 100644 --- a/servo/components/style/color/parsing.rs +++ b/servo/components/style/color/parsing.rs @@ -8,14 +8,34 @@ //! Relative colors, color-mix, system colors, and other such things require better calc() support //! and integration. -use crate::color::component::ColorComponent; -use cssparser::color::{ - clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE, +use super::{ + color_function::ColorFunction, + component::{ColorComponent, ColorComponentType}, + AbsoluteColor, +}; +use crate::{ + parser::ParserContext, + values::{ + generics::calc::CalcUnits, + specified::{ + angle::Angle as SpecifiedAngle, calc::Leaf as SpecifiedLeaf, + color::Color as SpecifiedColor, + }, + }, +}; +use cssparser::{ + color::{clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE}, + match_ignore_ascii_case, CowRcStr, Parser, Token, }; -use cssparser::{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. +#[inline] +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 { @@ -29,56 +49,39 @@ impl From<u8> for ColorComponent<u8> { /// CSS escaping (if relevant) should be resolved before calling this function. /// (For example, the value of an `Ident` token is fine.) #[inline] -pub fn parse_color_keyword<Output>(ident: &str) -> Result<Output, ()> -where - Output: FromParsedColor, -{ +pub fn parse_color_keyword(ident: &str) -> Result<SpecifiedColor, ()> { Ok(match_ignore_ascii_case! { ident, - "transparent" => Output::from_rgba( - 0u8.into(), - 0u8.into(), - 0u8.into(), - ColorComponent::Value(NumberOrPercentage::Number { value: 0.0 }), - ), - "currentcolor" => Output::from_current_color(), + "transparent" => { + SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(0u8, 0u8, 0u8, 0.0)) + }, + "currentcolor" => SpecifiedColor::CurrentColor, _ => { let (r, g, b) = cssparser::color::parse_named_color(ident)?; - Output::from_rgba( - r.into(), - g.into(), - b.into(), - ColorComponent::Value(NumberOrPercentage::Number { value: OPAQUE }), - ) + SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, OPAQUE)) }, }) } /// Parse a CSS color using the specified [`ColorParser`] and return a new color /// value on success. -pub fn parse_color_with<'i, 't, P>( - color_parser: &P, +pub fn parse_color_with<'i, 't>( + color_parser: &ColorParser<'_, '_>, input: &mut Parser<'i, 't>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<SpecifiedColor, ParseError<'i>> { let location = input.current_source_location(); let token = input.next()?; match *token { Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes()) .map(|(r, g, b, a)| { - P::Output::from_rgba( - r.into(), - g.into(), - b.into(), - ColorComponent::Value(NumberOrPercentage::Number { value: a }), - ) + SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)) }), Token::Ident(ref value) => parse_color_keyword(value), Token::Function(ref name) => { let name = name.clone(); return input.parse_nested_block(|arguments| { - parse_color_function(color_parser, name, arguments) + Ok(SpecifiedColor::from_absolute_color( + parse_color_function(color_parser, name, arguments)?.resolve_to_absolute(), + )) }); }, _ => Err(()), @@ -88,22 +91,19 @@ where /// Parse one of the color functions: rgba(), lab(), color(), etc. #[inline] -fn parse_color_function<'i, 't, P>( - color_parser: &P, +fn parse_color_function<'i, 't>( + color_parser: &ColorParser<'_, '_>, name: CowRcStr<'i>, arguments: &mut Parser<'i, 't>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { 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, P::Output::from_lab), - "lch" => parse_lch_like(color_parser, arguments, P::Output::from_lch), - "oklab" => parse_lab_like(color_parser, arguments, P::Output::from_oklab), - "oklch" => parse_lch_like(color_parser, arguments, P::Output::from_oklch), + "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), _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))), }?; @@ -113,13 +113,10 @@ where Ok(color) } -fn parse_legacy_alpha<'i, 't, P>( - color_parser: &P, +fn parse_legacy_alpha<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { if !arguments.is_exhausted() { arguments.expect_comma()?; color_parser.parse_number_or_percentage(arguments, false) @@ -130,13 +127,10 @@ where } } -fn parse_modern_alpha<'i, 't, P>( - color_parser: &P, +fn parse_modern_alpha<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { if !arguments.is_exhausted() { arguments.expect_delim('/')?; color_parser.parse_number_or_percentage(arguments, true) @@ -157,22 +151,45 @@ impl ColorComponent<NumberOrPercentage> { } } +/// Parse the relative color syntax "from" syntax `from <color>`. +fn parse_origin_color<'i, 't>( + color_parser: &ColorParser<'_, '_>, + arguments: &mut Parser<'i, 't>, +) -> Result<Option<SpecifiedColor>, ParseError<'i>> { + if !rcs_enabled() { + return Ok(None); + } + + // Not finding the from keyword is not an error, it just means we don't + // have an origin color. + if arguments + .try_parse(|p| p.expect_ident_matching("from")) + .is_err() + { + return Ok(None); + } + + // We still fail if we can't parse the origin color. + parse_color_with(color_parser, arguments).map(|color| Some(color)) +} + #[inline] -fn parse_rgb<'i, 't, P>( - color_parser: &P, +fn parse_rgb<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { + let origin_color = parse_origin_color(color_parser, arguments)?; + let location = arguments.current_source_location(); let maybe_red = color_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. - let is_legacy_syntax = - !maybe_red.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok(); + // are parsing the legacy syntax. Legacy syntax also doesn't support an + // origin color. + let is_legacy_syntax = 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 { let Ok(is_percentage) = maybe_red.is_percentage() else { @@ -217,25 +234,26 @@ where (red, green, blue, alpha) }; - Ok(P::Output::from_rgba(red, green, blue, alpha)) + Ok(ColorFunction::Rgb(red, green, blue, alpha)) } /// Parses hsl syntax. /// /// <https://drafts.csswg.org/css-color/#the-hsl-notation> #[inline] -fn parse_hsl<'i, 't, P>( - color_parser: &P, +fn parse_hsl<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { + let origin_color = parse_origin_color(color_parser, arguments)?; + let hue = color_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. - let is_legacy_syntax = !hue.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok(); + // the legacy syntax. Legacy syntax also doesn't support an origin color. + let is_legacy_syntax = 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 @@ -260,29 +278,28 @@ where ) }; - Ok(P::Output::from_hsl(hue, saturation, lightness, alpha)) + Ok(ColorFunction::Hsl(hue, saturation, lightness, alpha)) } /// Parses hwb syntax. /// /// <https://drafts.csswg.org/css-color/#the-hbw-notation> #[inline] -fn parse_hwb<'i, 't, P>( - color_parser: &P, +fn parse_hwb<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { + let _origin_color = parse_origin_color(color_parser, arguments)?; + let (hue, whiteness, blackness, alpha) = parse_components( color_parser, arguments, - P::parse_number_or_angle, - P::parse_number_or_percentage, - P::parse_number_or_percentage, + ColorParser::parse_number_or_angle, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, )?; - Ok(P::Output::from_hwb(hue, whiteness, blackness, alpha)) + Ok(ColorFunction::Hwb(hue, whiteness, blackness, alpha)) } type IntoLabFn<Output> = fn( @@ -293,20 +310,19 @@ type IntoLabFn<Output> = fn( ) -> Output; #[inline] -fn parse_lab_like<'i, 't, P>( - color_parser: &P, +fn parse_lab_like<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, - into_color: IntoLabFn<P::Output>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ + into_color: IntoLabFn<ColorFunction>, +) -> Result<ColorFunction, ParseError<'i>> { + let _origin_color = parse_origin_color(color_parser, arguments)?; + let (lightness, a, b, alpha) = parse_components( color_parser, arguments, - P::parse_number_or_percentage, - P::parse_number_or_percentage, - P::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, )?; Ok(into_color(lightness, a, b, alpha)) @@ -320,20 +336,19 @@ type IntoLchFn<Output> = fn( ) -> Output; #[inline] -fn parse_lch_like<'i, 't, P>( - color_parser: &P, +fn parse_lch_like<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, - into_color: IntoLchFn<P::Output>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ + into_color: IntoLchFn<ColorFunction>, +) -> Result<ColorFunction, ParseError<'i>> { + let _origin_color = parse_origin_color(color_parser, arguments)?; + let (lightness, chroma, hue, alpha) = parse_components( color_parser, arguments, - P::parse_number_or_percentage, - P::parse_number_or_percentage, - P::parse_number_or_angle, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_angle, )?; Ok(into_color(lightness, chroma, hue, alpha)) @@ -341,13 +356,12 @@ where /// Parse the color() function. #[inline] -fn parse_color_with_color_space<'i, 't, P>( - color_parser: &P, +fn parse_color_with_color_space<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { + let _origin_color = parse_origin_color(color_parser, arguments)?; + let color_space = { let location = arguments.current_source_location(); @@ -359,18 +373,12 @@ where let (c1, c2, c3, alpha) = parse_components( color_parser, arguments, - P::parse_number_or_percentage, - P::parse_number_or_percentage, - P::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, )?; - Ok(P::Output::from_color_function( - color_space, - c1, - c2, - c3, - alpha, - )) + Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha)) } type ComponentParseResult<'i, R1, R2, R3> = Result< @@ -384,18 +392,29 @@ type ComponentParseResult<'i, R1, R2, R3> = Result< >; /// Parse the color components and alpha with the modern [color-4] syntax. -pub fn parse_components<'i, 't, P, F1, F2, F3, R1, R2, R3>( - color_parser: &P, +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 - P: ColorParser<'i>, - F1: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R1>, ParseError<'i>>, - F2: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R2>, ParseError<'i>>, - F3: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R3>, ParseError<'i>>, + 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)?; @@ -407,6 +426,7 @@ where } /// Either a number or a percentage. +#[derive(Clone, Copy, Debug)] pub enum NumberOrPercentage { /// `<number>`. Number { @@ -432,7 +452,32 @@ impl NumberOrPercentage { } } +impl ColorComponentType for NumberOrPercentage { + fn units() -> CalcUnits { + CalcUnits::PERCENTAGE + } + + fn try_from_token(token: &Token) -> Result<Self, ()> { + Ok(match *token { + Token::Number { value, .. } => Self::Number { value }, + Token::Percentage { unit_value, .. } => Self::Percentage { unit_value }, + _ => { + return Err(()); + }, + }) + } + + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result<Self, ()> { + Ok(match *leaf { + SpecifiedLeaf::Percentage(unit_value) => Self::Percentage { unit_value }, + SpecifiedLeaf::Number(value) => Self::Number { value }, + _ => return Err(()), + }) + } +} + /// Either an angle or a number. +#[derive(Clone, Copy, Debug)] pub enum NumberOrAngle { /// `<number>`. Number { @@ -457,45 +502,118 @@ impl NumberOrAngle { } } -/// A trait that can be used to hook into how `cssparser` parses color -/// components, with the intention of implementing more complicated behavior. -/// -/// For example, this is used by Servo to support calc() in color. -pub trait ColorParser<'i> { - /// The type that the parser will construct on a successful parse. - type Output: FromParsedColor; - - /// Parse an `<angle>` or `<number>`. - /// - /// Returns the result in degrees. - fn parse_number_or_angle<'t>( +impl ColorComponentType for NumberOrAngle { + fn units() -> CalcUnits { + CalcUnits::ANGLE + } + + fn try_from_token(token: &Token) -> Result<Self, ()> { + Ok(match *token { + Token::Number { value, .. } => Self::Number { value }, + Token::Dimension { + value, ref unit, .. + } => { + let degrees = + SpecifiedAngle::parse_dimension(value, unit, /* from_calc = */ false) + .map(|angle| angle.degrees())?; + + NumberOrAngle::Angle { degrees } + }, + _ => { + return Err(()); + }, + }) + } + + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result<Self, ()> { + Ok(match *leaf { + SpecifiedLeaf::Angle(angle) => Self::Angle { + degrees: angle.degrees(), + }, + SpecifiedLeaf::Number(value) => Self::Number { value }, + _ => return Err(()), + }) + } +} + +/// The raw f32 here is for <number>. +impl ColorComponentType for f32 { + fn units() -> CalcUnits { + CalcUnits::empty() + } + + fn try_from_token(token: &Token) -> Result<Self, ()> { + if let Token::Number { value, .. } = *token { + Ok(value) + } else { + Err(()) + } + } + + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result<Self, ()> { + if let SpecifiedLeaf::Number(value) = *leaf { + Ok(value) + } else { + Err(()) + } + } +} + +/// Used to parse the components of a color. +pub struct ColorParser<'a, 'b: 'a> { + /// Parser context used for parsing the colors. + pub context: &'a ParserContext<'b>, +} + +impl<'a, 'b: 'a> ColorParser<'a, 'b> { + /// 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>>; + ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> { + ColorComponent::parse(self.context, input, allow_none) + } /// Parse a `<percentage>` value. - /// - /// Returns the result in a number from 0.0 to 1.0. - fn parse_percentage<'t>( + fn parse_percentage<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result<ColorComponent<f32>, ParseError<'i>>; + ) -> Result<ColorComponent<f32>, ParseError<'i>> { + let location = input.current_source_location(); + + // We can use the [NumberOrPercentage] type here, because parsing it + // doesn't have any more overhead than just parsing a percentage on its + // own. + Ok( + match ColorComponent::<NumberOrPercentage>::parse(self.context, input, allow_none)? { + ColorComponent::None => ColorComponent::None, + ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) => { + ColorComponent::Value(unit_value) + }, + _ => return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + }, + ) + } /// Parse a `<number>` value. - fn parse_number<'t>( + fn parse_number<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result<ColorComponent<f32>, ParseError<'i>>; + ) -> Result<ColorComponent<f32>, ParseError<'i>> { + ColorComponent::parse(self.context, input, allow_none) + } - /// Parse a `<number>` value or a `<percentage>` value. - fn parse_number_or_percentage<'t>( + /// Parse a `<number>` or `<percentage>` value. + fn parse_number_or_percentage<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>; + ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { + ColorComponent::parse(self.context, input, allow_none) + } } /// This trait is used by the [`ColorParser`] to construct colors of any type. |