Bug 1885912 - Store a ColorFunction in a specified Color. r=layout-reviewers,emilio

When we have a color that can't be resolved at parse time (or should not
be resolved) then we store the data that was parsed as is to preserve
all the data for round tripping.

Differential Revision: https://phabricator.services.mozilla.com/D220268
This commit is contained in:
Tiaan Louw 2024-10-16 13:45:17 +00:00
parent 380f90df2c
commit 51a68a20d7
8 changed files with 597 additions and 333 deletions

View File

@ -4,21 +4,23 @@
//! Output of parsing a color function, e.g. rgb(..), hsl(..), color(..)
use crate::{color::ColorFlags, values::normalize};
use cssparser::color::{clamp_floor_256_f32, OPAQUE};
use std::fmt::Write;
use super::{
component::ColorComponent,
convert::normalize_hue,
parsing::{NumberOrAngle, NumberOrPercentage},
AbsoluteColor, ColorSpace,
AbsoluteColor, ColorFlags, ColorSpace,
};
use crate::values::{normalize, specified::color::Color as SpecifiedColor};
use cssparser::color::{clamp_floor_256_f32, OPAQUE};
/// Represents a specified color function.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum ColorFunction {
/// <https://drafts.csswg.org/css-color-4/#rgb-functions>
Rgb(
Option<SpecifiedColor>, // origin
ColorComponent<NumberOrPercentage>, // red
ColorComponent<NumberOrPercentage>, // green
ColorComponent<NumberOrPercentage>, // blue
@ -26,22 +28,23 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#the-hsl-notation>
Hsl(
Option<SpecifiedColor>, // origin
ColorComponent<NumberOrAngle>, // hue
ColorComponent<NumberOrPercentage>, // saturation
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // alpha
bool, // is_legacy_syntax
),
/// <https://drafts.csswg.org/css-color-4/#the-hwb-notation>
Hwb(
Option<SpecifiedColor>, // origin
ColorComponent<NumberOrAngle>, // hue
ColorComponent<NumberOrPercentage>, // whiteness
ColorComponent<NumberOrPercentage>, // blackness
ColorComponent<NumberOrPercentage>, // alpha
bool, // is_legacy_syntax
),
/// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch>
Lab(
Option<SpecifiedColor>, // origin
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // a
ColorComponent<NumberOrPercentage>, // b
@ -49,6 +52,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch>
Lch(
Option<SpecifiedColor>, // origin
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // chroma
ColorComponent<NumberOrAngle>, // hue
@ -56,6 +60,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch>
Oklab(
Option<SpecifiedColor>, // origin
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // a
ColorComponent<NumberOrPercentage>, // b
@ -63,6 +68,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch>
Oklch(
Option<SpecifiedColor>, // origin
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // chroma
ColorComponent<NumberOrAngle>, // hue
@ -70,171 +76,402 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#color-function>
Color(
ColorSpace,
Option<SpecifiedColor>, // origin
ColorComponent<NumberOrPercentage>, // red / x
ColorComponent<NumberOrPercentage>, // green / y
ColorComponent<NumberOrPercentage>, // blue / z
ColorComponent<NumberOrPercentage>, // alpha
ColorSpace,
),
}
impl ColorFunction {
/// Return true if the color funciton has an origin color specified.
pub fn has_origin_color(&self) -> bool {
match self {
Self::Rgb(origin_color, ..) |
Self::Hsl(origin_color, ..) |
Self::Hwb(origin_color, ..) |
Self::Lab(origin_color, ..) |
Self::Lch(origin_color, ..) |
Self::Oklab(origin_color, ..) |
Self::Oklch(origin_color, ..) |
Self::Color(origin_color, ..) => origin_color.is_some(),
}
}
/// 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) -> Result<AbsoluteColor, ()> {
macro_rules! alpha {
($alpha:expr) => {{
($alpha:expr, $origin_color:expr) => {{
$alpha
.resolve(None)?
.resolve($origin_color)?
.map(|value| normalize(value.to_number(1.0)).clamp(0.0, OPAQUE))
}};
}
macro_rules! resolved_origin_color {
($origin_color:expr,$color_space:expr) => {{
match $origin_color {
Some(color) => color
.resolve_to_absolute()
.map(|color| color.to_color_space($color_space)),
None => None,
}
}};
}
Ok(match self {
ColorFunction::Rgb(r, g, b, alpha) => {
ColorFunction::Rgb(origin_color, r, g, b, alpha) => {
#[inline]
fn resolve(component: &ColorComponent<NumberOrPercentage>) -> Result<u8, ()> {
// TODO(tlouw): We need to pass an origin color to resolve.
fn resolve(
component: &ColorComponent<NumberOrPercentage>,
origin_color: Option<&AbsoluteColor>,
) -> Result<u8, ()> {
Ok(clamp_floor_256_f32(
component
.resolve(None)?
.resolve(origin_color)?
.map(|value| value.to_number(u8::MAX as f32))
.unwrap_or(0.0),
))
}
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Srgb);
AbsoluteColor::srgb_legacy(
resolve(r)?,
resolve(g)?,
resolve(b)?,
alpha!(alpha).unwrap_or(0.0),
resolve(r, origin_color.as_ref())?,
resolve(g, origin_color.as_ref())?,
resolve(b, origin_color.as_ref())?,
alpha!(alpha, origin_color.as_ref()).unwrap_or(0.0),
)
},
ColorFunction::Hsl(h, s, l, alpha, is_legacy_syntax) => {
ColorFunction::Hsl(origin_color, 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;
// If the origin color:
// - was *NOT* specified, then we stick with the old way of serializing the
// value to rgb(..).
// - was specified, we don't use the rgb(..) syntax, because we should allow the
// color to be out of gamut and not clamp.
let use_rgb_sytax = origin_color.is_none();
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Hsl);
let mut result = AbsoluteColor::new(
ColorSpace::Hsl,
h.resolve(None)?.map(|angle| normalize_hue(angle.degrees())),
s.resolve(None)?.map(|s| {
if *is_legacy_syntax {
h.resolve(origin_color.as_ref())?
.map(|angle| normalize_hue(angle.degrees())),
s.resolve(origin_color.as_ref())?.map(|s| {
if use_rgb_sytax {
s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)
} else {
s.to_number(SATURATION_RANGE)
}
}),
l.resolve(None)?.map(|l| {
if *is_legacy_syntax {
l.resolve(origin_color.as_ref())?.map(|l| {
if use_rgb_sytax {
l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)
} else {
l.to_number(LIGHTNESS_RANGE)
}
}),
alpha!(alpha),
alpha!(alpha, origin_color.as_ref()),
);
if *is_legacy_syntax {
if use_rgb_sytax {
result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
}
result
},
ColorFunction::Hwb(h, w, b, alpha, is_legacy_syntax) => {
ColorFunction::Hwb(origin_color, h, w, b, alpha) => {
// If the origin color:
// - was *NOT* specified, then we stick with the old way of serializing the
// value to rgb(..).
// - was specified, we don't use the rgb(..) syntax, because we should allow the
// color to be out of gamut and not clamp.
let use_rgb_sytax = origin_color.is_none();
// 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 origin_color = resolved_origin_color!(origin_color, ColorSpace::Hwb);
let mut result = AbsoluteColor::new(
ColorSpace::Hwb,
h.resolve(None)?.map(|angle| normalize_hue(angle.degrees())),
w.resolve(None)?.map(|w| {
if *is_legacy_syntax {
h.resolve(origin_color.as_ref())?
.map(|angle| normalize_hue(angle.degrees())),
w.resolve(origin_color.as_ref())?.map(|w| {
if use_rgb_sytax {
w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)
} else {
w.to_number(WHITENESS_RANGE)
}
}),
b.resolve(None)?.map(|b| {
if *is_legacy_syntax {
b.resolve(origin_color.as_ref())?.map(|b| {
if use_rgb_sytax {
b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)
} else {
b.to_number(BLACKNESS_RANGE)
}
}),
alpha!(alpha),
alpha!(alpha, origin_color.as_ref()),
);
if *is_legacy_syntax {
if use_rgb_sytax {
result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
}
result
},
ColorFunction::Lab(l, a, b, alpha) => {
ColorFunction::Lab(origin_color, 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;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Lab);
AbsoluteColor::new(
ColorSpace::Lab,
l.resolve(None)?.map(|l| l.to_number(LIGHTNESS_RANGE)),
a.resolve(None)?.map(|a| a.to_number(A_B_RANGE)),
b.resolve(None)?.map(|b| b.to_number(A_B_RANGE)),
alpha!(alpha),
l.resolve(origin_color.as_ref())?
.map(|l| l.to_number(LIGHTNESS_RANGE)),
a.resolve(origin_color.as_ref())?
.map(|a| a.to_number(A_B_RANGE)),
b.resolve(origin_color.as_ref())?
.map(|b| b.to_number(A_B_RANGE)),
alpha!(alpha, origin_color.as_ref()),
)
},
ColorFunction::Lch(l, c, h, alpha) => {
ColorFunction::Lch(origin_color, 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;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Lch);
AbsoluteColor::new(
ColorSpace::Lch,
l.resolve(None)?.map(|l| l.to_number(LIGHTNESS_RANGE)),
c.resolve(None)?.map(|c| c.to_number(CHROMA_RANGE)),
h.resolve(None)?.map(|angle| normalize_hue(angle.degrees())),
alpha!(alpha),
l.resolve(origin_color.as_ref())?
.map(|l| l.to_number(LIGHTNESS_RANGE)),
c.resolve(origin_color.as_ref())?
.map(|c| c.to_number(CHROMA_RANGE)),
h.resolve(origin_color.as_ref())?
.map(|angle| normalize_hue(angle.degrees())),
alpha!(alpha, origin_color.as_ref()),
)
},
ColorFunction::Oklab(l, a, b, alpha) => {
ColorFunction::Oklab(origin_color, 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;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Oklab);
AbsoluteColor::new(
ColorSpace::Oklab,
l.resolve(None)?.map(|l| l.to_number(LIGHTNESS_RANGE)),
a.resolve(None)?.map(|a| a.to_number(A_B_RANGE)),
b.resolve(None)?.map(|b| b.to_number(A_B_RANGE)),
alpha!(alpha),
l.resolve(origin_color.as_ref())?
.map(|l| l.to_number(LIGHTNESS_RANGE)),
a.resolve(origin_color.as_ref())?
.map(|a| a.to_number(A_B_RANGE)),
b.resolve(origin_color.as_ref())?
.map(|b| b.to_number(A_B_RANGE)),
alpha!(alpha, origin_color.as_ref()),
)
},
ColorFunction::Oklch(l, c, h, alpha) => {
ColorFunction::Oklch(origin_color, 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;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Oklch);
AbsoluteColor::new(
ColorSpace::Oklch,
l.resolve(None)?.map(|l| l.to_number(LIGHTNESS_RANGE)),
c.resolve(None)?.map(|c| c.to_number(CHROMA_RANGE)),
h.resolve(None)?.map(|angle| normalize_hue(angle.degrees())),
alpha!(alpha),
l.resolve(origin_color.as_ref())?
.map(|l| l.to_number(LIGHTNESS_RANGE)),
c.resolve(origin_color.as_ref())?
.map(|c| c.to_number(CHROMA_RANGE)),
h.resolve(origin_color.as_ref())?
.map(|angle| normalize_hue(angle.degrees())),
alpha!(alpha, origin_color.as_ref()),
)
},
ColorFunction::Color(origin_color, r, g, b, alpha, color_space) => {
let origin_color = resolved_origin_color!(origin_color, *color_space);
AbsoluteColor::new(
(*color_space).into(),
r.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
g.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
b.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
alpha!(alpha, origin_color.as_ref()),
)
},
ColorFunction::Color(color_space, r, g, b, alpha) => AbsoluteColor::new(
(*color_space).into(),
r.resolve(None)?.map(|c| c.to_number(1.0)),
g.resolve(None)?.map(|c| c.to_number(1.0)),
b.resolve(None)?.map(|c| c.to_number(1.0)),
alpha!(alpha),
),
})
}
}
impl style_traits::ToCss for ColorFunction {
fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
where
W: std::fmt::Write,
{
let (origin_color, alpha) = match self {
Self::Rgb(origin_color, _, _, _, alpha) => {
dest.write_str("rgb(")?;
(origin_color, alpha)
},
Self::Hsl(origin_color, _, _, _, alpha) => {
dest.write_str("hsl(")?;
(origin_color, alpha)
},
Self::Hwb(origin_color, _, _, _, alpha) => {
dest.write_str("hwb(")?;
(origin_color, alpha)
},
Self::Lab(origin_color, _, _, _, alpha) => {
dest.write_str("lab(")?;
(origin_color, alpha)
},
Self::Lch(origin_color, _, _, _, alpha) => {
dest.write_str("lch(")?;
(origin_color, alpha)
},
Self::Oklab(origin_color, _, _, _, alpha) => {
dest.write_str("oklab(")?;
(origin_color, alpha)
},
Self::Oklch(origin_color, _, _, _, alpha) => {
dest.write_str("oklch(")?;
(origin_color, alpha)
},
Self::Color(origin_color, _, _, _, alpha, _) => {
dest.write_str("color(")?;
(origin_color, alpha)
},
};
if let Some(origin_color) = origin_color {
dest.write_str("from ")?;
origin_color.to_css(dest)?;
dest.write_str(" ")?;
}
let is_opaque = if let ColorComponent::Value(value) = *alpha {
value.to_number(OPAQUE) == OPAQUE
} else {
false
};
match self {
Self::Rgb(_, r, g, b, alpha) => {
r.to_css(dest)?;
dest.write_str(" ")?;
g.to_css(dest)?;
dest.write_str(" ")?;
b.to_css(dest)?;
if !is_opaque {
dest.write_str(" / ")?;
alpha.to_css(dest)?;
}
},
Self::Hsl(_, h, s, l, alpha) => {
h.to_css(dest)?;
dest.write_str(" ")?;
s.to_css(dest)?;
dest.write_str(" ")?;
l.to_css(dest)?;
if !is_opaque {
dest.write_str(" / ")?;
alpha.to_css(dest)?;
}
},
Self::Hwb(_, h, w, b, alpha) => {
h.to_css(dest)?;
dest.write_str(" ")?;
w.to_css(dest)?;
dest.write_str(" ")?;
b.to_css(dest)?;
if !is_opaque {
dest.write_str(" / ")?;
alpha.to_css(dest)?;
}
},
Self::Lab(_, l, a, b, alpha) => {
l.to_css(dest)?;
dest.write_str(" ")?;
a.to_css(dest)?;
dest.write_str(" ")?;
b.to_css(dest)?;
if !is_opaque {
dest.write_str(" / ")?;
alpha.to_css(dest)?;
}
},
Self::Lch(_, l, c, h, alpha) => {
l.to_css(dest)?;
dest.write_str(" ")?;
c.to_css(dest)?;
dest.write_str(" ")?;
h.to_css(dest)?;
if !is_opaque {
dest.write_str(" / ")?;
alpha.to_css(dest)?;
}
},
Self::Oklab(_, l, a, b, alpha) => {
l.to_css(dest)?;
dest.write_str(" ")?;
a.to_css(dest)?;
dest.write_str(" ")?;
b.to_css(dest)?;
if !is_opaque {
dest.write_str(" / ")?;
alpha.to_css(dest)?;
}
},
Self::Oklch(_, l, c, h, alpha) => {
l.to_css(dest)?;
dest.write_str(" ")?;
c.to_css(dest)?;
dest.write_str(" ")?;
h.to_css(dest)?;
if !is_opaque {
dest.write_str(" / ")?;
alpha.to_css(dest)?;
}
},
Self::Color(_, r, g, b, alpha, color_space) => {
color_space.to_css(dest)?;
dest.write_str(" ")?;
r.to_css(dest)?;
dest.write_str(" ")?;
g.to_css(dest)?;
dest.write_str(" ")?;
b.to_css(dest)?;
if !is_opaque {
dest.write_str(" / ")?;
alpha.to_css(dest)?;
}
},
}
dest.write_str(")")
}
}

View File

@ -4,6 +4,8 @@
//! Parse/serialize and resolve a single color component.
use std::fmt::Write;
use super::{
parsing::{rcs_enabled, ChannelKeyword},
AbsoluteColor,
@ -16,7 +18,7 @@ use crate::{
},
};
use cssparser::{Parser, Token};
use style_traits::ParseError;
use style_traits::{ParseError, StyleParseErrorKind, ToCss};
/// A single color component.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
@ -63,7 +65,6 @@ 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();
@ -71,16 +72,11 @@ 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() => {
ref t @ Token::Ident(ref ident) => {
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()))
}
Ok(ColorComponent::Calc(Box::new(SpecifiedCalcNode::Leaf(
SpecifiedLeaf::ColorComponent(channel_keyword),
))))
} else {
Err(location.new_unexpected_token_error(t.clone()))
}
@ -93,6 +89,7 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
ValueType::units()
};
let mut node = SpecifiedCalcNode::parse(context, input, function, units)?;
// TODO(tlouw): We only have to simplify the node when we have to store it, but we
// only know if we have to store it much later when the whole color
// can't be resolved to absolute at which point the calc nodes are
@ -108,7 +105,7 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
}
/// Resolve a [ColorComponent] into a float. None is "none".
pub fn resolve(&self, origin_color: Option<AbsoluteColor>) -> Result<Option<ValueType>, ()> {
pub fn resolve(&self, origin_color: Option<&AbsoluteColor>) -> Result<Option<ValueType>, ()> {
Ok(match self {
ColorComponent::None => None,
ColorComponent::Value(value) => Some(value.clone()),
@ -139,3 +136,31 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
})
}
}
impl<ValueType: ToCss> ToCss for ColorComponent<ValueType> {
fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
where
W: Write,
{
match self {
ColorComponent::None => dest.write_str("none")?,
ColorComponent::Value(value) => value.to_css(dest)?,
ColorComponent::Calc(node) => {
// Channel keywords used directly as a component serializes without `calc()`, but
// we store channel keywords inside a calc node irrespectively, so we have to remove
// it again here.
// There are some contradicting wpt's, which depends on resolution of:
// <https://github.com/web-platform-tests/wpt/issues/47921>
if let SpecifiedCalcNode::Leaf(SpecifiedLeaf::ColorComponent(channel_keyword)) =
node.as_ref()
{
channel_keyword.to_css(dest)?;
} else {
node.to_css(dest)?;
}
},
}
Ok(())
}
}

View File

@ -16,6 +16,7 @@ pub mod parsing;
mod to_css;
use self::parsing::ChannelKeyword;
pub use color_function::*;
use component::ColorComponent;
use cssparser::color::PredefinedColorSpace;

View File

@ -9,7 +9,7 @@
use super::{
color_function::ColorFunction,
component::{ColorComponent, ColorComponentType},
AbsoluteColor, ColorFlags, ColorSpace,
AbsoluteColor,
};
use crate::{
parser::{Parse, ParserContext},
@ -101,13 +101,17 @@ pub fn parse_color_with<'i, 't>(
let name = name.clone();
return input.parse_nested_block(|arguments| {
let color_function = parse_color_function(context, name, arguments)?;
// TODO(tlouw): A color function can be valid, but not resolvable. This check
// assumes that if we can't resolve it, it's invalid.
// TODO(tlouw): Specified colors should not be resolved here and stored as is.
if let Ok(resolved) = color_function.resolve_to_absolute() {
if color_function.has_origin_color() {
// Preserve the color as it was parsed.
Ok(SpecifiedColor::ColorFunction(Box::new(color_function)))
} else if let Ok(resolved) = color_function.resolve_to_absolute() {
Ok(SpecifiedColor::from_absolute_color(resolved))
} else {
// We should store the unresolvable value here in the specifed color.
// This will only happen when the parsed color contains errors like calc units
// that cannot be resolved at parse time, but will fail when trying to resolve
// them, etc. This should be rare, but for now just failing the color value
// makes sense.
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
});
@ -126,20 +130,15 @@ fn parse_color_function<'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(&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),
"rgb" | "rgba" => parse_rgb(context, arguments, origin_color),
"hsl" | "hsla" => parse_hsl(context, arguments, origin_color),
"hwb" => parse_hwb(context, arguments, origin_color),
"lab" => parse_lab_like(context, arguments, origin_color, ColorFunction::Lab),
"lch" => parse_lch_like(context, arguments, origin_color, ColorFunction::Lch),
"oklab" => parse_lab_like(context, arguments, origin_color, ColorFunction::Oklab),
"oklch" => parse_lch_like(context, arguments, origin_color, ColorFunction::Oklch),
"color" => parse_color_with_color_space(context, arguments, origin_color),
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
}?;
@ -152,7 +151,7 @@ fn parse_color_function<'i, 't>(
fn parse_origin_color<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
) -> Result<Option<AbsoluteColor>, ParseError<'i>> {
) -> Result<Option<SpecifiedColor>, ParseError<'i>> {
if !rcs_enabled() {
return Ok(None);
}
@ -166,66 +165,47 @@ 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.
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)))
SpecifiedColor::parse(context, arguments).map(Option::Some)
}
#[inline]
fn parse_rgb<'i, 't>(
component_parser: &ComponentParser<'_, '_>,
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction, ParseError<'i>> {
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 maybe_red = component_parser.parse_number_or_percentage(arguments, true)?;
let maybe_red = parse_number_or_percentage(context, 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 = component_parser.origin_color.is_none() &&
let is_legacy_syntax = origin_color.is_none() &&
!maybe_red.is_none() &&
arguments.try_parse(|p| p.expect_comma()).is_ok();
Ok(if is_legacy_syntax {
let (green, blue) = if maybe_red.is_percentage() {
let green = component_parser.parse_percentage(arguments, false)?;
let green = parse_percentage(context, arguments, false)?;
arguments.expect_comma()?;
let blue = component_parser.parse_percentage(arguments, false)?;
let blue = parse_percentage(context, arguments, false)?;
(green, blue)
} else {
let green = component_parser.parse_number(arguments, false)?;
let green = parse_number(context, arguments, false)?;
arguments.expect_comma()?;
let blue = component_parser.parse_number(arguments, false)?;
let blue = parse_number(context, arguments, false)?;
(green, blue)
};
let alpha = component_parser.parse_legacy_alpha(arguments)?;
let alpha = parse_legacy_alpha(context, arguments)?;
ColorFunction::Rgb(maybe_red, green, blue, alpha)
ColorFunction::Rgb(origin_color, maybe_red, green, blue, alpha)
} else {
let green = component_parser.parse_number_or_percentage(arguments, true)?;
let blue = component_parser.parse_number_or_percentage(arguments, true)?;
let green = parse_number_or_percentage(context, arguments, true)?;
let blue = parse_number_or_percentage(context, arguments, true)?;
let alpha = component_parser.parse_modern_alpha(arguments)?;
let alpha = parse_modern_alpha(context, arguments)?;
ColorFunction::Rgb(maybe_red, green, blue, alpha)
ColorFunction::Rgb(origin_color, maybe_red, green, blue, alpha)
})
}
@ -234,43 +214,37 @@ fn parse_rgb<'i, 't>(
/// <https://drafts.csswg.org/css-color/#the-hsl-notation>
#[inline]
fn parse_hsl<'i, 't>(
component_parser: &ComponentParser<'_, '_>,
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction, ParseError<'i>> {
let component_parser = ComponentParser {
context: component_parser.context,
origin_color: component_parser
.origin_color
.map(|c| c.to_color_space(ColorSpace::Hsl)),
};
let hue = component_parser.parse_number_or_angle(arguments, true)?;
let hue = parse_number_or_angle(context, 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 = component_parser.origin_color.is_none() &&
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 = component_parser.parse_percentage(arguments, false)?;
let saturation = parse_percentage(context, arguments, false)?;
arguments.expect_comma()?;
let lightness = component_parser.parse_percentage(arguments, false)?;
let alpha = component_parser.parse_legacy_alpha(arguments)?;
let lightness = parse_percentage(context, arguments, false)?;
let alpha = parse_legacy_alpha(context, arguments)?;
(saturation, lightness, alpha)
} else {
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)?;
let saturation = parse_number_or_percentage(context, arguments, true)?;
let lightness = parse_number_or_percentage(context, arguments, true)?;
let alpha = parse_modern_alpha(context, arguments)?;
(saturation, lightness, alpha)
};
Ok(ColorFunction::Hsl(
origin_color,
hue,
saturation,
lightness,
alpha,
component_parser.origin_color.is_none(),
))
}
@ -279,32 +253,27 @@ fn parse_hsl<'i, 't>(
/// <https://drafts.csswg.org/css-color/#the-hbw-notation>
#[inline]
fn parse_hwb<'i, 't>(
component_parser: &ComponentParser<'_, '_>,
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction, ParseError<'i>> {
let component_parser = ComponentParser {
context: component_parser.context,
origin_color: component_parser
.origin_color
.map(|c| c.to_color_space(ColorSpace::Hwb)),
};
let hue = parse_number_or_angle(context, arguments, true)?;
let whiteness = parse_number_or_percentage(context, arguments, true)?;
let blackness = parse_number_or_percentage(context, arguments, true)?;
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)?;
let alpha = component_parser.parse_modern_alpha(arguments)?;
let alpha = parse_modern_alpha(context, arguments)?;
Ok(ColorFunction::Hwb(
origin_color,
hue,
whiteness,
blackness,
alpha,
component_parser.origin_color.is_none(),
))
}
type IntoLabFn<Output> = fn(
origin: Option<SpecifiedColor>,
l: ColorComponent<NumberOrPercentage>,
a: ColorComponent<NumberOrPercentage>,
b: ColorComponent<NumberOrPercentage>,
@ -313,28 +282,22 @@ type IntoLabFn<Output> = fn(
#[inline]
fn parse_lab_like<'i, 't>(
component_parser: &ComponentParser<'_, '_>,
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
color_space: ColorSpace,
origin_color: Option<SpecifiedColor>,
into_color: IntoLabFn<ColorFunction>,
) -> Result<ColorFunction, ParseError<'i>> {
let component_parser = ComponentParser {
context: component_parser.context,
origin_color: component_parser
.origin_color
.map(|c| c.to_color_space(color_space)),
};
let lightness = parse_number_or_percentage(context, arguments, true)?;
let a = parse_number_or_percentage(context, arguments, true)?;
let b = parse_number_or_percentage(context, arguments, true)?;
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 = parse_modern_alpha(context, arguments)?;
let alpha = component_parser.parse_modern_alpha(arguments)?;
Ok(into_color(lightness, a, b, alpha))
Ok(into_color(origin_color, lightness, a, b, alpha))
}
type IntoLchFn<Output> = fn(
origin: Option<SpecifiedColor>,
l: ColorComponent<NumberOrPercentage>,
a: ColorComponent<NumberOrPercentage>,
b: ColorComponent<NumberOrAngle>,
@ -343,55 +306,46 @@ type IntoLchFn<Output> = fn(
#[inline]
fn parse_lch_like<'i, 't>(
component_parser: &ComponentParser<'_, '_>,
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
color_space: ColorSpace,
origin_color: Option<SpecifiedColor>,
into_color: IntoLchFn<ColorFunction>,
) -> Result<ColorFunction, ParseError<'i>> {
let component_parser = ComponentParser {
context: component_parser.context,
origin_color: component_parser
.origin_color
.map(|c| c.to_color_space(color_space)),
};
let lightness = parse_number_or_percentage(context, arguments, true)?;
let chroma = parse_number_or_percentage(context, arguments, true)?;
let hue = parse_number_or_angle(context, arguments, true)?;
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 = parse_modern_alpha(context, arguments)?;
let alpha = component_parser.parse_modern_alpha(arguments)?;
Ok(into_color(lightness, chroma, hue, alpha))
Ok(into_color(origin_color, lightness, chroma, hue, alpha))
}
/// Parse the color() function.
#[inline]
fn parse_color_with_color_space<'i, 't>(
component_parser: &ComponentParser<'_, '_>,
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction, ParseError<'i>> {
let color_space = PredefinedColorSpace::parse(arguments)?;
let component_parser = ComponentParser {
context: component_parser.context,
origin_color: component_parser.origin_color.map(|c| {
// If the origin color was in legacy srgb, converting it won't
// change it to modern syntax. So make sure it's in modern syntax.
let mut c = c.to_color_space(ColorSpace::from(color_space));
c.flags.remove(ColorFlags::IS_LEGACY_SRGB);
c
}),
};
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)?;
let c1 = parse_number_or_percentage(context, arguments, true)?;
let c2 = parse_number_or_percentage(context, arguments, true)?;
let c3 = parse_number_or_percentage(context, arguments, true)?;
let alpha = component_parser.parse_modern_alpha(arguments)?;
let alpha = parse_modern_alpha(context, arguments)?;
Ok(ColorFunction::Color(color_space.into(), c1, c2, c3, alpha))
Ok(ColorFunction::Color(
origin_color,
c1,
c2,
c3,
alpha,
color_space.into(),
))
}
/// Either a number or a percentage.
/// Eithee.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum NumberOrPercentage {
/// `<number>`.
@ -524,109 +478,79 @@ impl ColorComponentType for f32 {
}
}
/// Used to parse the components of a color.
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>,
/// Parse an `<number>` or `<angle>` value.
fn parse_number_or_angle<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> {
ColorComponent::parse(context, input, allow_none)
}
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 a `<percentage>` value.
fn parse_percentage<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
let location = input.current_source_location();
let value = ColorComponent::<NumberOrPercentage>::parse(context, input, allow_none)?;
if !value.is_percentage() {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
/// 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, self.origin_color.as_ref())
Ok(value)
}
/// Parse a `<number>` value.
fn parse_number<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
let location = input.current_source_location();
let value = ColorComponent::<NumberOrPercentage>::parse(context, input, allow_none)?;
if !value.is_number() {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
/// Parse a `<percentage>` value.
fn parse_percentage<'i, 't>(
&self,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
let location = input.current_source_location();
Ok(value)
}
let value = ColorComponent::<NumberOrPercentage>::parse(
self.context,
input,
allow_none,
self.origin_color.as_ref(),
)?;
/// Parse a `<number>` or `<percentage>` value.
fn parse_number_or_percentage<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
ColorComponent::parse(context, input, allow_none)
}
if !value.is_percentage() {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(value)
fn parse_legacy_alpha<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
if !arguments.is_exhausted() {
arguments.expect_comma()?;
parse_number_or_percentage(context, arguments, false)
} else {
Ok(ColorComponent::Value(NumberOrPercentage::Number(OPAQUE)))
}
}
/// Parse a `<number>` value.
fn parse_number<'i, 't>(
&self,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
let location = input.current_source_location();
let value = ColorComponent::<NumberOrPercentage>::parse(
self.context,
input,
allow_none,
self.origin_color.as_ref(),
)?;
if !value.is_number() {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(value)
}
/// 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>> {
ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref())
}
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(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(
self.origin_color.map(|c| c.alpha).unwrap_or(OPAQUE),
)))
}
fn parse_modern_alpha<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
if !arguments.is_exhausted() {
arguments.expect_delim('/')?;
parse_number_or_percentage(context, arguments, true)
} else {
Ok(ColorComponent::Value(NumberOrPercentage::Number(OPAQUE)))
}
}

View File

@ -4,7 +4,10 @@
//! Write colors into CSS strings.
use super::{AbsoluteColor, ColorFlags, ColorSpace};
use super::{
parsing::{NumberOrAngle, NumberOrPercentage},
AbsoluteColor, ColorFlags, ColorSpace,
};
use crate::values::normalize;
use cssparser::color::{clamp_unit_f32, serialize_color_alpha, OPAQUE};
use std::fmt::{self, Write};
@ -38,6 +41,36 @@ impl<'a> ToCss for ModernComponent<'a> {
}
}
impl ToCss for NumberOrPercentage {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
use crate::values::computed::Percentage;
match self {
Self::Number(number) => number.to_css(dest)?,
Self::Percentage(percentage) => Percentage(*percentage).to_css(dest)?,
}
Ok(())
}
}
impl ToCss for NumberOrAngle {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
use crate::values::computed::Angle;
match self {
Self::Number(number) => number.to_css(dest)?,
Self::Angle(degrees) => Angle::from_degrees(*degrees).to_css(dest)?,
}
Ok(())
}
}
impl ToCss for AbsoluteColor {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where

View File

@ -158,6 +158,7 @@ pub enum SortKey {
Vmax,
Vmin,
Vw,
ColorComponent,
Other,
}
@ -1429,14 +1430,14 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
if value_or_stop!(children[0].is_nan_leaf()) {
replace_self_with!(&mut children[0]);
return
return;
}
let mut result = 0;
for i in 1..children.len() {
if value_or_stop!(children[i].is_nan_leaf()) {
replace_self_with!(&mut children[i]);
return
return;
}
let o = match children[i]
.compare(&children[result], PositivePercentageBasis::Unknown)

View File

@ -231,7 +231,7 @@ impl generic::CalcNodeLeaf for Leaf {
fn sort_key(&self) -> SortKey {
match *self {
Self::Number(..) | Self::ColorComponent(..) => SortKey::Number,
Self::Number(..) => SortKey::Number,
Self::Percentage(..) => SortKey::Percentage,
Self::Time(..) => SortKey::Sec,
Self::Resolution(..) => SortKey::Dppx,
@ -284,6 +284,7 @@ impl generic::CalcNodeLeaf for Leaf {
},
NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
},
Self::ColorComponent(..) => SortKey::ColorComponent,
}
}

View File

@ -6,7 +6,7 @@
use super::AllowQuirks;
use crate::color::mix::ColorInterpolationMethod;
use crate::color::{parsing, AbsoluteColor, ColorSpace};
use crate::color::{parsing, AbsoluteColor, ColorFlags, ColorFunction, ColorSpace};
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
@ -115,6 +115,9 @@ pub enum Color {
/// An absolute color.
/// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-function
Absolute(Box<Absolute>),
/// A color function that could not be resolved to a [Color::Absolute] color at parse time.
/// Right now this is only the case for relative colors with `currentColor` as the origin.
ColorFunction(Box<ColorFunction>),
/// A system color.
#[cfg(feature = "gecko")]
System(SystemColor),
@ -141,7 +144,9 @@ impl LightDark {
fn compute(&self, cx: &Context) -> ComputedColor {
let dark = cx.device().is_dark_color_scheme(cx.builder.color_scheme);
if cx.for_non_inherited_property {
cx.rule_cache_conditions.borrow_mut().set_color_scheme_dependency(cx.builder.color_scheme);
cx.rule_cache_conditions
.borrow_mut()
.set_color_scheme_dependency(cx.builder.color_scheme);
}
let used = if dark { &self.dark } else { &self.light };
used.to_computed_value(cx)
@ -422,7 +427,9 @@ impl SystemColor {
let color = cx.device().system_nscolor(*self, cx.builder.color_scheme);
if cx.for_non_inherited_property {
cx.rule_cache_conditions.borrow_mut().set_color_scheme_dependency(cx.builder.color_scheme);
cx.rule_cache_conditions
.borrow_mut()
.set_color_scheme_dependency(cx.builder.color_scheme);
}
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
return ComputedColor::currentcolor();
@ -565,6 +572,7 @@ impl ToCss for Color {
match *self {
Color::CurrentColor => dest.write_str("currentcolor"),
Color::Absolute(ref absolute) => absolute.to_css(dest),
Color::ColorFunction(ref color_function) => color_function.to_css(dest),
Color::ColorMix(ref mix) => mix.to_css(dest),
Color::LightDark(ref ld) => ld.to_css(dest),
#[cfg(feature = "gecko")]
@ -585,6 +593,14 @@ impl Color {
#[cfg(feature = "gecko")]
Self::System(..) => true,
Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
Self::ColorFunction(ref color_function) => {
// For now we allow transparent colors if we can resolve the color function.
// <https://bugzilla.mozilla.org/show_bug.cgi?id=1923053>
color_function
.resolve_to_absolute()
.map(|resolved| allow_transparent && resolved.is_transparent())
.unwrap_or(false)
},
Self::LightDark(ref ld) => {
ld.light.honored_in_forced_colors_mode(allow_transparent) &&
ld.dark.honored_in_forced_colors_mode(allow_transparent)
@ -625,25 +641,22 @@ impl Color {
use crate::values::specified::percentage::ToPercentage;
match self {
Self::Absolute(c) => return Some(c.color),
Self::Absolute(c) => Some(c.color),
Self::ColorFunction(ref color_function) => color_function.resolve_to_absolute().ok(),
Self::ColorMix(ref mix) => {
if let Some(left) = mix.left.resolve_to_absolute() {
if let Some(right) = mix.right.resolve_to_absolute() {
return Some(crate::color::mix::mix(
mix.interpolation,
&left,
mix.left_percentage.to_percentage(),
&right,
mix.right_percentage.to_percentage(),
mix.flags,
));
}
}
let left = mix.left.resolve_to_absolute()?;
let right = mix.right.resolve_to_absolute()?;
Some(crate::color::mix::mix(
mix.interpolation,
&left,
mix.left_percentage.to_percentage(),
&right,
mix.right_percentage.to_percentage(),
mix.flags,
))
},
_ => (),
};
None
_ => None,
}
}
/// Parse a color, with quirks.
@ -746,28 +759,57 @@ impl Color {
/// If `context` is `None`, and the specified color requires data from
/// the context to resolve, then `None` is returned.
pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
macro_rules! adjust_absolute_color {
($color:expr) => {{
// Computed lightness values can not be NaN.
if matches!(
$color.color_space,
ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
) {
$color.components.0 = normalize($color.components.0);
}
// Computed RGB and XYZ components can not be NaN.
if !$color.is_legacy_syntax() && $color.color_space.is_rgb_or_xyz_like() {
$color.components = $color.components.map(normalize);
}
$color.alpha = normalize($color.alpha);
}};
}
Some(match *self {
Color::CurrentColor => ComputedColor::CurrentColor,
Color::Absolute(ref absolute) => {
let mut color = absolute.color;
// Computed lightness values can not be NaN.
if matches!(
color.color_space,
ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
) {
color.components.0 = normalize(color.components.0);
}
// Computed RGB and XYZ components can not be NaN.
if !color.is_legacy_syntax() && color.color_space.is_rgb_or_xyz_like() {
color.components = color.components.map(normalize);
}
color.alpha = normalize(color.alpha);
adjust_absolute_color!(color);
ComputedColor::Absolute(color)
},
Color::ColorFunction(ref color_function) => {
let has_origin_color = color_function.has_origin_color();
let Ok(mut absolute) = color_function.resolve_to_absolute() else {
// TODO(tlouw): The specified color must contain `currentColor` or some other
// unresolvable origin color, so here we have to store the whole
// [ColorFunction] in the computed color.
return Some(ComputedColor::Absolute(AbsoluteColor::BLACK));
};
// A special case when the color was a rgb(..) function and had an origin color,
// the result must be in the color(srgb ..) syntax, to avoid clipped channels
// into gamut limits.
let mut absolute = match absolute.color_space {
_ if has_origin_color && absolute.is_legacy_syntax() => {
absolute.flags.remove(ColorFlags::IS_LEGACY_SRGB);
absolute
},
_ => absolute,
};
adjust_absolute_color!(absolute);
ComputedColor::Absolute(absolute)
},
Color::LightDark(ref ld) => ld.compute(context?),
Color::ColorMix(ref mix) => {
use crate::values::computed::percentage::Percentage;