summaryrefslogtreecommitdiffstats
path: root/servo/components/style/color
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:37 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:37 +0000
commita90a5cba08fdf6c0ceb95101c275108a152a3aed (patch)
tree532507288f3defd7f4dcf1af49698bcb76034855 /servo/components/style/color
parentAdding debian version 126.0.1-1. (diff)
downloadfirefox-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.rs28
-rw-r--r--servo/components/style/color/component.rs52
-rw-r--r--servo/components/style/color/mod.rs71
-rw-r--r--servo/components/style/color/parsing.rs516
-rw-r--r--servo/components/style/color/to_css.rs8
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")?;