summaryrefslogtreecommitdiffstats
path: root/servo/components/style/values/specified/color.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/values/specified/color.rs')
-rw-r--r--servo/components/style/values/specified/color.rs335
1 files changed, 273 insertions, 62 deletions
diff --git a/servo/components/style/values/specified/color.rs b/servo/components/style/values/specified/color.rs
index 3a19a2f4a3..3694b4e9bc 100644
--- a/servo/components/style/values/specified/color.rs
+++ b/servo/components/style/values/specified/color.rs
@@ -5,19 +5,21 @@
//! Specified color values.
use super::AllowQuirks;
-use crate::color::parsing::{
- self, AngleOrNumber, Color as CSSParserColor, FromParsedColor, NumberOrPercentage,
-};
+use crate::color::component::ColorComponent;
+use crate::color::convert::normalize_hue;
+use crate::color::parsing::{self, FromParsedColor, NumberOrAngle, NumberOrPercentage};
use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace};
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
+use crate::values::generics::calc::CalcUnits;
use crate::values::generics::color::{
ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorOrAuto,
};
-use crate::values::specified::calc::CalcNode;
+use crate::values::specified::calc::{CalcNode, Leaf};
use crate::values::specified::Percentage;
use crate::values::{normalize, CustomIdent};
+use cssparser::color::OPAQUE;
use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token};
use itoa;
use std::fmt::{self, Write};
@@ -428,94 +430,217 @@ impl SystemColor {
}
}
+impl<T> From<ColorComponent<T>> for Option<T> {
+ fn from(value: ColorComponent<T>) -> Self {
+ match value {
+ ColorComponent::None => None,
+ ColorComponent::Value(value) => Some(value),
+ }
+ }
+}
+
+impl ColorComponent<NumberOrPercentage> {
+ #[inline]
+ fn into_alpha(self) -> Option<f32> {
+ match self {
+ ColorComponent::None => None,
+ ColorComponent::Value(number_or_percentage) => {
+ Some(normalize(number_or_percentage.to_number(1.0)).clamp(0.0, OPAQUE))
+ },
+ }
+ }
+}
+
impl FromParsedColor for Color {
fn from_current_color() -> Self {
Color::CurrentColor
}
- fn from_rgba(r: u8, g: u8, b: u8, a: f32) -> Self {
- AbsoluteColor::srgb_legacy(r, g, b, a).into()
+ fn from_rgba(
+ red: ColorComponent<u8>,
+ green: ColorComponent<u8>,
+ blue: ColorComponent<u8>,
+ alpha: ColorComponent<NumberOrPercentage>,
+ ) -> Self {
+ macro_rules! c {
+ ($c:expr) => {{
+ match $c {
+ ColorComponent::None => 0u8,
+ ColorComponent::Value(value) => value,
+ }
+ }};
+ }
+
+ // Legacy rgb() doesn't support "none" alpha values and falls back to 0.
+ let alpha = alpha.into_alpha().unwrap_or(0.0);
+
+ AbsoluteColor::srgb_legacy(c!(red), c!(green), c!(blue), alpha).into()
}
fn from_hsl(
- hue: Option<f32>,
- saturation: Option<f32>,
- lightness: Option<f32>,
- alpha: Option<f32>,
+ hue: ColorComponent<NumberOrAngle>,
+ saturation: ColorComponent<NumberOrPercentage>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Hsl, hue, saturation, lightness, alpha).into()
+ // 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;
+
+ let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
+ let saturation =
+ saturation.map_value(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE));
+ let lightness =
+ lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE));
+
+ AbsoluteColor::new(
+ ColorSpace::Hsl,
+ hue,
+ saturation,
+ lightness,
+ alpha.into_alpha(),
+ )
+ .into()
}
fn from_hwb(
- hue: Option<f32>,
- whiteness: Option<f32>,
- blackness: Option<f32>,
- alpha: Option<f32>,
+ hue: ColorComponent<NumberOrAngle>,
+ whiteness: ColorComponent<NumberOrPercentage>,
+ blackness: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Hwb, hue, whiteness, blackness, alpha).into()
+ // 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;
+
+ let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
+ let whiteness =
+ whiteness.map_value(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE));
+ let blackness =
+ blackness.map_value(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE));
+
+ AbsoluteColor::new(
+ ColorSpace::Hwb,
+ hue,
+ whiteness,
+ blackness,
+ alpha.into_alpha(),
+ )
+ .into()
}
fn from_lab(
- lightness: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ a: ColorComponent<NumberOrPercentage>,
+ b: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha).into()
+ // 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;
+
+ let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
+ let a = a.map_value(|a| a.to_number(A_B_RANGE));
+ let b = b.map_value(|b| b.to_number(A_B_RANGE));
+
+ AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha.into_alpha()).into()
}
fn from_lch(
- lightness: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ chroma: ColorComponent<NumberOrPercentage>,
+ hue: ColorComponent<NumberOrAngle>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha).into()
+ // 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;
+
+ let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
+ let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE));
+ let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
+
+ AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha.into_alpha()).into()
}
fn from_oklab(
- lightness: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ a: ColorComponent<NumberOrPercentage>,
+ b: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha).into()
+ // 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;
+
+ let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
+ let a = a.map_value(|a| a.to_number(A_B_RANGE));
+ let b = b.map_value(|b| b.to_number(A_B_RANGE));
+
+ AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha.into_alpha()).into()
}
fn from_oklch(
- lightness: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ chroma: ColorComponent<NumberOrPercentage>,
+ hue: ColorComponent<NumberOrAngle>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Oklch, lightness, chroma, hue, alpha).into()
+ // 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;
+
+ let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
+ let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE));
+ let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
+
+ AbsoluteColor::new(
+ ColorSpace::Oklch,
+ lightness,
+ chroma,
+ hue,
+ alpha.into_alpha(),
+ )
+ .into()
}
fn from_color_function(
color_space: PredefinedColorSpace,
- c1: Option<f32>,
- c2: Option<f32>,
- c3: Option<f32>,
- alpha: Option<f32>,
+ c1: ColorComponent<NumberOrPercentage>,
+ c2: ColorComponent<NumberOrPercentage>,
+ c3: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha).into()
+ let c1 = c1.map_value(|c| c.to_number(1.0));
+ let c2 = c2.map_value(|c| c.to_number(1.0));
+ let c3 = c3.map_value(|c| c.to_number(1.0));
+
+ AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha.into_alpha()).into()
}
}
struct ColorParser<'a, 'b: 'a>(&'a ParserContext<'b>);
+
impl<'a, 'b: 'a, 'i: 'a> parsing::ColorParser<'i> for ColorParser<'a, 'b> {
type Output = Color;
- type Error = StyleParseErrorKind<'i>;
- fn parse_angle_or_number<'t>(
+ fn parse_number_or_angle<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<AngleOrNumber, ParseError<'i>> {
+ allow_none: bool,
+ ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> {
use crate::values::specified::Angle;
let location = input.current_source_location();
let token = input.next()?.clone();
- match token {
+ Ok(match token {
+ Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
+ ColorComponent::None
+ },
Token::Dimension {
value, ref unit, ..
} => {
@@ -526,44 +651,125 @@ impl<'a, 'b: 'a, 'i: 'a> parsing::ColorParser<'i> for ColorParser<'a, 'b> {
Err(()) => return Err(location.new_unexpected_token_error(token.clone())),
};
- Ok(AngleOrNumber::Angle { degrees })
+ ColorComponent::Value(NumberOrAngle::Angle { degrees })
},
- Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }),
+ Token::Number { value, .. } => ColorComponent::Value(NumberOrAngle::Number { value }),
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
- CalcNode::parse_angle_or_number(self.0, input, function)
+ let node = CalcNode::parse(self.0, input, function, CalcUnits::ANGLE)?;
+
+ // If we can resolve the calc node, then use the value.
+ match node.resolve() {
+ Ok(Leaf::Number(value)) => {
+ ColorComponent::Value(NumberOrAngle::Number { value })
+ },
+ Ok(Leaf::Angle(angle)) => ColorComponent::Value(NumberOrAngle::Angle {
+ degrees: angle.degrees(),
+ }),
+ _ => {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
+ },
+ }
},
t => return Err(location.new_unexpected_token_error(t)),
- }
+ })
}
- fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
- Ok(Percentage::parse(self.0, input)?.get())
+ fn parse_percentage<'t>(
+ &self,
+ input: &mut Parser<'i, 't>,
+ allow_none: bool,
+ ) -> Result<ColorComponent<f32>, ParseError<'i>> {
+ let location = input.current_source_location();
+
+ Ok(match *input.next()? {
+ Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
+ ColorComponent::None
+ },
+ Token::Percentage { unit_value, .. } => ColorComponent::Value(unit_value),
+ Token::Function(ref name) => {
+ let function = CalcNode::math_function(self.0, name, location)?;
+ let node = CalcNode::parse(self.0, input, function, CalcUnits::PERCENTAGE)?;
+
+ // If we can resolve the calc node, then use the value.
+ let Ok(resolved_leaf) = node.resolve() else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ };
+ if let Leaf::Percentage(value) = resolved_leaf {
+ ColorComponent::Value(value)
+ } else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+ },
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ })
}
- fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
- use crate::values::specified::Number;
+ fn parse_number<'t>(
+ &self,
+ input: &mut Parser<'i, 't>,
+ allow_none: bool,
+ ) -> Result<ColorComponent<f32>, ParseError<'i>> {
+ let location = input.current_source_location();
+
+ Ok(match *input.next()? {
+ Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
+ ColorComponent::None
+ },
+ Token::Number { value, .. } => ColorComponent::Value(value),
+ Token::Function(ref name) => {
+ let function = CalcNode::math_function(self.0, name, location)?;
+ let node = CalcNode::parse(self.0, input, function, CalcUnits::empty())?;
- Ok(Number::parse(self.0, input)?.get())
+ // If we can resolve the calc node, then use the value.
+ let Ok(resolved_leaf) = node.resolve() else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ };
+ if let Leaf::Number(value) = resolved_leaf {
+ ColorComponent::Value(value)
+ } else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+ },
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ })
}
fn parse_number_or_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<NumberOrPercentage, ParseError<'i>> {
+ allow_none: bool,
+ ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
let location = input.current_source_location();
- match *input.next()? {
- Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }),
+ Ok(match *input.next()? {
+ Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
+ ColorComponent::None
+ },
+ Token::Number { value, .. } => {
+ ColorComponent::Value(NumberOrPercentage::Number { value })
+ },
Token::Percentage { unit_value, .. } => {
- Ok(NumberOrPercentage::Percentage { unit_value })
+ ColorComponent::Value(NumberOrPercentage::Percentage { unit_value })
},
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
- CalcNode::parse_number_or_percentage(self.0, input, function)
+ let node = CalcNode::parse(self.0, input, function, CalcUnits::PERCENTAGE)?;
+
+ // If we can resolve the calc node, then use the value.
+ let Ok(resolved_leaf) = node.resolve() else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ };
+ if let Leaf::Percentage(unit_value) = resolved_leaf {
+ ColorComponent::Value(NumberOrPercentage::Percentage { unit_value })
+ } else if let Leaf::Number(value) = resolved_leaf {
+ ColorComponent::Value(NumberOrPercentage::Number { value })
+ } else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
},
ref t => return Err(location.new_unexpected_token_error(t.clone())),
- }
+ })
}
}
@@ -700,7 +906,7 @@ impl ToCss for Color {
W: Write,
{
match *self {
- Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest),
+ Color::CurrentColor => dest.write_str("currentcolor"),
Color::Absolute(ref absolute) => absolute.to_css(dest),
Color::ColorMix(ref mix) => mix.to_css(dest),
Color::LightDark(ref ld) => ld.to_css(dest),
@@ -772,7 +978,12 @@ impl Color {
loc: &cssparser::SourceLocation,
) -> Result<Self, ParseError<'i>> {
match cssparser::color::parse_hash_color(bytes) {
- Ok((r, g, b, a)) => Ok(Self::from_rgba(r, g, b, a)),
+ Ok((r, g, b, a)) => Ok(Self::from_rgba(
+ r.into(),
+ g.into(),
+ b.into(),
+ ColorComponent::Value(NumberOrPercentage::Number { value: a }),
+ )),
Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}