mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
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:
parent
380f90df2c
commit
51a68a20d7
@ -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(")")
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user