forked from mirrors/gecko-dev
Backed out 3 changesets (bug 1892188) for wpt failure on color-computed-relative-color.html . CLOSED TREE
Backed out changeset 712905edaaef (bug 1892188) Backed out changeset 032a5034f221 (bug 1892188) Backed out changeset 2f28639aaae1 (bug 1892188)
This commit is contained in:
parent
51ec86ec8c
commit
8d67200f2f
2 changed files with 493 additions and 112 deletions
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Parsing for CSS colors.
|
||||
//! Fairly complete css-color implementation.
|
||||
//! Relative colors, color-mix, system colors, and other such things require better calc() support
|
||||
//! and integration.
|
||||
|
||||
use super::{
|
||||
color_function::ColorFunction,
|
||||
component::{ColorComponent, ColorComponentType},
|
||||
AbsoluteColor,
|
||||
AbsoluteColor, ColorSpace,
|
||||
};
|
||||
use crate::{
|
||||
parser::ParserContext,
|
||||
|
|
@ -34,6 +36,13 @@ pub fn rcs_enabled() -> bool {
|
|||
static_prefs::pref!("layout.css.relative-color-syntax.enabled")
|
||||
}
|
||||
|
||||
impl From<u8> for ColorComponent<u8> {
|
||||
#[inline]
|
||||
fn from(value: u8) -> Self {
|
||||
ColorComponent::Value(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the named color with the given name.
|
||||
///
|
||||
/// Matching is case-insensitive in the ASCII range.
|
||||
|
|
@ -56,7 +65,7 @@ pub fn parse_color_keyword(ident: &str) -> Result<SpecifiedColor, ()> {
|
|||
/// Parse a CSS color using the specified [`ColorParser`] and return a new color
|
||||
/// value on success.
|
||||
pub fn parse_color_with<'i, 't>(
|
||||
context: &ParserContext,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<SpecifiedColor, ParseError<'i>> {
|
||||
let location = input.current_source_location();
|
||||
|
|
@ -71,7 +80,7 @@ pub fn parse_color_with<'i, 't>(
|
|||
let name = name.clone();
|
||||
return input.parse_nested_block(|arguments| {
|
||||
Ok(SpecifiedColor::from_absolute_color(
|
||||
parse_color_function(context, name, arguments)?.resolve_to_absolute(),
|
||||
parse_color_function(color_parser, name, arguments)?.resolve_to_absolute(),
|
||||
))
|
||||
});
|
||||
},
|
||||
|
|
@ -83,26 +92,25 @@ pub fn parse_color_with<'i, 't>(
|
|||
/// Parse one of the color functions: rgba(), lab(), color(), etc.
|
||||
#[inline]
|
||||
fn parse_color_function<'i, 't>(
|
||||
context: &ParserContext,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
name: CowRcStr<'i>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<ColorFunction, ParseError<'i>> {
|
||||
let origin_color = parse_origin_color(context, arguments)?;
|
||||
|
||||
let component_parser = ComponentParser {
|
||||
context,
|
||||
origin_color,
|
||||
let origin_color = parse_origin_color(color_parser, arguments)?;
|
||||
let color_parser = ColorParser {
|
||||
context: color_parser.context,
|
||||
origin_color: origin_color.as_ref(),
|
||||
};
|
||||
|
||||
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, ColorFunction::Lab),
|
||||
"lch" => parse_lch_like(&component_parser, arguments, ColorFunction::Lch),
|
||||
"oklab" => parse_lab_like(&component_parser, arguments, ColorFunction::Oklab),
|
||||
"oklch" => parse_lch_like(&component_parser, arguments, ColorFunction::Oklch),
|
||||
"color" =>parse_color_with_color_space(&component_parser, arguments),
|
||||
"rgb" | "rgba" => parse_rgb(&color_parser, arguments),
|
||||
"hsl" | "hsla" => parse_hsl(&color_parser, arguments),
|
||||
"hwb" => parse_hwb(&color_parser, arguments),
|
||||
"lab" => parse_lab_like(&color_parser, arguments, ColorSpace::Lab, ColorFunction::Lab),
|
||||
"lch" => parse_lch_like(&color_parser, arguments, ColorSpace::Lch, ColorFunction::Lch),
|
||||
"oklab" => parse_lab_like(&color_parser, arguments, ColorSpace::Oklab, ColorFunction::Oklab),
|
||||
"oklch" => parse_lch_like(&color_parser, arguments, ColorSpace::Oklch, ColorFunction::Oklch),
|
||||
"color" => parse_color_with_color_space(&color_parser, arguments),
|
||||
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
|
||||
}?;
|
||||
|
||||
|
|
@ -111,6 +119,34 @@ fn parse_color_function<'i, 't>(
|
|||
Ok(color)
|
||||
}
|
||||
|
||||
fn parse_legacy_alpha<'i, 't>(
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
|
||||
if !arguments.is_exhausted() {
|
||||
arguments.expect_comma()?;
|
||||
color_parser.parse_number_or_percentage(arguments, false)
|
||||
} else {
|
||||
Ok(ColorComponent::Value(NumberOrPercentage::Number {
|
||||
value: OPAQUE,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_modern_alpha<'i, 't>(
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
|
||||
if !arguments.is_exhausted() {
|
||||
arguments.expect_delim('/')?;
|
||||
color_parser.parse_number_or_percentage(arguments, true)
|
||||
} else {
|
||||
Ok(ColorComponent::Value(NumberOrPercentage::Number {
|
||||
value: OPAQUE,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorComponent<NumberOrPercentage> {
|
||||
/// Return true if the component contains a percentage.
|
||||
pub fn is_percentage(&self) -> Result<bool, ()> {
|
||||
|
|
@ -123,7 +159,7 @@ impl ColorComponent<NumberOrPercentage> {
|
|||
|
||||
/// Parse the relative color syntax "from" syntax `from <color>`.
|
||||
fn parse_origin_color<'i, 't>(
|
||||
context: &ParserContext,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<Option<AbsoluteColor>, ParseError<'i>> {
|
||||
if !rcs_enabled() {
|
||||
|
|
@ -142,7 +178,7 @@ fn parse_origin_color<'i, 't>(
|
|||
let location = arguments.current_source_location();
|
||||
|
||||
// We still fail if we can't parse the origin color.
|
||||
let origin_color = parse_color_with(context, arguments)?;
|
||||
let origin_color = parse_color_with(color_parser, arguments)?;
|
||||
|
||||
// Right now we only handle absolute colors.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1890972
|
||||
|
|
@ -155,17 +191,25 @@ fn parse_origin_color<'i, 't>(
|
|||
|
||||
#[inline]
|
||||
fn parse_rgb<'i, 't>(
|
||||
component_parser: &ComponentParser<'_, '_>,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<ColorFunction, ParseError<'i>> {
|
||||
let origin_color = color_parser
|
||||
.origin_color
|
||||
.map(|c| c.to_color_space(ColorSpace::Srgb));
|
||||
let color_parser = ColorParser {
|
||||
context: color_parser.context,
|
||||
origin_color: origin_color.as_ref(),
|
||||
};
|
||||
|
||||
let location = arguments.current_source_location();
|
||||
|
||||
let maybe_red = component_parser.parse_number_or_percentage(arguments, true)?;
|
||||
let maybe_red = color_parser.parse_number_or_percentage(arguments, true)?;
|
||||
|
||||
// If the first component is not "none" and is followed by a comma, then we
|
||||
// are parsing the legacy syntax. Legacy syntax also doesn't support an
|
||||
// origin color.
|
||||
let is_legacy_syntax = component_parser.origin_color.is_none() &&
|
||||
let is_legacy_syntax = color_parser.origin_color.is_none() &&
|
||||
!maybe_red.is_none() &&
|
||||
arguments.try_parse(|p| p.expect_comma()).is_ok();
|
||||
|
||||
|
|
@ -175,38 +219,37 @@ fn parse_rgb<'i, 't>(
|
|||
};
|
||||
let (red, green, blue) = if is_percentage {
|
||||
let red = maybe_red.map_value(|v| clamp_unit_f32(v.to_number(1.0)));
|
||||
let green = component_parser
|
||||
let green = color_parser
|
||||
.parse_percentage(arguments, false)?
|
||||
.map_value(clamp_unit_f32);
|
||||
arguments.expect_comma()?;
|
||||
let blue = component_parser
|
||||
let blue = color_parser
|
||||
.parse_percentage(arguments, false)?
|
||||
.map_value(clamp_unit_f32);
|
||||
(red, green, blue)
|
||||
} else {
|
||||
let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
|
||||
let green = component_parser
|
||||
let green = color_parser
|
||||
.parse_number(arguments, false)?
|
||||
.map_value(clamp_floor_256_f32);
|
||||
arguments.expect_comma()?;
|
||||
let blue = component_parser
|
||||
let blue = color_parser
|
||||
.parse_number(arguments, false)?
|
||||
.map_value(clamp_floor_256_f32);
|
||||
(red, green, blue)
|
||||
};
|
||||
|
||||
let alpha = component_parser.parse_legacy_alpha(arguments)?;
|
||||
let alpha = parse_legacy_alpha(&color_parser, arguments)?;
|
||||
|
||||
ColorFunction::Rgb(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 alpha = component_parser.parse_modern_alpha(arguments)?;
|
||||
let green = color_parser.parse_number_or_percentage(arguments, true)?;
|
||||
let blue = color_parser.parse_number_or_percentage(arguments, true)?;
|
||||
let alpha = parse_modern_alpha(&color_parser, arguments)?;
|
||||
|
||||
// When using the relative color syntax (having an origin color), the
|
||||
// resulting color is always in the modern syntax.
|
||||
if component_parser.origin_color.is_some() {
|
||||
if color_parser.origin_color.is_some() {
|
||||
ColorFunction::Color(PredefinedColorSpace::Srgb, maybe_red, green, blue, alpha)
|
||||
} else {
|
||||
fn clamp(v: NumberOrPercentage) -> u8 {
|
||||
|
|
@ -227,32 +270,46 @@ fn parse_rgb<'i, 't>(
|
|||
/// <https://drafts.csswg.org/css-color/#the-hsl-notation>
|
||||
#[inline]
|
||||
fn parse_hsl<'i, 't>(
|
||||
component_parser: &ComponentParser<'_, '_>,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<ColorFunction, ParseError<'i>> {
|
||||
let hue = component_parser.parse_number_or_angle(arguments, true)?;
|
||||
let origin_color = color_parser
|
||||
.origin_color
|
||||
.map(|c| c.to_color_space(ColorSpace::Hsl));
|
||||
let color_parser = ColorParser {
|
||||
context: color_parser.context,
|
||||
origin_color: origin_color.as_ref(),
|
||||
};
|
||||
|
||||
let hue = color_parser.parse_number_or_angle(arguments, true)?;
|
||||
|
||||
// If the hue is not "none" and is followed by a comma, then we are parsing
|
||||
// the legacy syntax. Legacy syntax also doesn't support an origin color.
|
||||
let is_legacy_syntax = component_parser.origin_color.is_none() &&
|
||||
let is_legacy_syntax = color_parser.origin_color.is_none() &&
|
||||
!hue.is_none() &&
|
||||
arguments.try_parse(|p| p.expect_comma()).is_ok();
|
||||
|
||||
let (saturation, lightness, alpha) = if is_legacy_syntax {
|
||||
let saturation = component_parser
|
||||
let saturation = color_parser
|
||||
.parse_percentage(arguments, false)?
|
||||
.map_value(|unit_value| NumberOrPercentage::Percentage { unit_value });
|
||||
arguments.expect_comma()?;
|
||||
let lightness = component_parser
|
||||
let lightness = color_parser
|
||||
.parse_percentage(arguments, false)?
|
||||
.map_value(|unit_value| NumberOrPercentage::Percentage { unit_value });
|
||||
let alpha = component_parser.parse_legacy_alpha(arguments)?;
|
||||
(saturation, lightness, alpha)
|
||||
(
|
||||
saturation,
|
||||
lightness,
|
||||
parse_legacy_alpha(&color_parser, arguments)?,
|
||||
)
|
||||
} 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)?;
|
||||
(saturation, lightness, alpha)
|
||||
let saturation = color_parser.parse_number_or_percentage(arguments, true)?;
|
||||
let lightness = color_parser.parse_number_or_percentage(arguments, true)?;
|
||||
(
|
||||
saturation,
|
||||
lightness,
|
||||
parse_modern_alpha(&color_parser, arguments)?,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(ColorFunction::Hsl(hue, saturation, lightness, alpha))
|
||||
|
|
@ -263,14 +320,24 @@ fn parse_hsl<'i, 't>(
|
|||
/// <https://drafts.csswg.org/css-color/#the-hbw-notation>
|
||||
#[inline]
|
||||
fn parse_hwb<'i, 't>(
|
||||
component_parser: &ComponentParser<'_, '_>,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<ColorFunction, ParseError<'i>> {
|
||||
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 origin_color = color_parser
|
||||
.origin_color
|
||||
.map(|c| c.to_color_space(ColorSpace::Hwb));
|
||||
let color_parser = ColorParser {
|
||||
context: color_parser.context,
|
||||
origin_color: origin_color.as_ref(),
|
||||
};
|
||||
|
||||
let alpha = component_parser.parse_modern_alpha(arguments)?;
|
||||
let (hue, whiteness, blackness, alpha) = parse_components(
|
||||
&color_parser,
|
||||
arguments,
|
||||
ColorParser::parse_number_or_angle,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
)?;
|
||||
|
||||
Ok(ColorFunction::Hwb(hue, whiteness, blackness, alpha))
|
||||
}
|
||||
|
|
@ -284,15 +351,26 @@ type IntoLabFn<Output> = fn(
|
|||
|
||||
#[inline]
|
||||
fn parse_lab_like<'i, 't>(
|
||||
component_parser: &ComponentParser<'_, '_>,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
color_space: ColorSpace,
|
||||
into_color: IntoLabFn<ColorFunction>,
|
||||
) -> Result<ColorFunction, ParseError<'i>> {
|
||||
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 origin_color = color_parser
|
||||
.origin_color
|
||||
.map(|c| c.to_color_space(color_space));
|
||||
let color_parser = ColorParser {
|
||||
context: color_parser.context,
|
||||
origin_color: origin_color.as_ref(),
|
||||
};
|
||||
|
||||
let alpha = component_parser.parse_modern_alpha(arguments)?;
|
||||
let (lightness, a, b, alpha) = parse_components(
|
||||
&color_parser,
|
||||
arguments,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
)?;
|
||||
|
||||
Ok(into_color(lightness, a, b, alpha))
|
||||
}
|
||||
|
|
@ -306,15 +384,26 @@ type IntoLchFn<Output> = fn(
|
|||
|
||||
#[inline]
|
||||
fn parse_lch_like<'i, 't>(
|
||||
component_parser: &ComponentParser<'_, '_>,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
color_space: ColorSpace,
|
||||
into_color: IntoLchFn<ColorFunction>,
|
||||
) -> Result<ColorFunction, ParseError<'i>> {
|
||||
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 origin_color = color_parser
|
||||
.origin_color
|
||||
.map(|c| c.to_color_space(color_space));
|
||||
let color_parser = ColorParser {
|
||||
context: color_parser.context,
|
||||
origin_color: origin_color.as_ref(),
|
||||
};
|
||||
|
||||
let alpha = component_parser.parse_modern_alpha(arguments)?;
|
||||
let (lightness, chroma, hue, alpha) = parse_components(
|
||||
&color_parser,
|
||||
arguments,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
ColorParser::parse_number_or_angle,
|
||||
)?;
|
||||
|
||||
Ok(into_color(lightness, chroma, hue, alpha))
|
||||
}
|
||||
|
|
@ -322,7 +411,7 @@ fn parse_lch_like<'i, 't>(
|
|||
/// Parse the color() function.
|
||||
#[inline]
|
||||
fn parse_color_with_color_space<'i, 't>(
|
||||
component_parser: &ComponentParser<'_, '_>,
|
||||
color_parser: &ColorParser<'_, '_>,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<ColorFunction, ParseError<'i>> {
|
||||
let color_space = {
|
||||
|
|
@ -333,23 +422,69 @@ fn parse_color_with_color_space<'i, 't>(
|
|||
.map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))?
|
||||
};
|
||||
|
||||
let origin_color = component_parser
|
||||
let origin_color = color_parser
|
||||
.origin_color
|
||||
.map(|c| c.to_color_space(color_space.into()));
|
||||
let component_parser = ComponentParser {
|
||||
context: component_parser.context,
|
||||
origin_color,
|
||||
let color_parser = ColorParser {
|
||||
context: color_parser.context,
|
||||
origin_color: origin_color.as_ref(),
|
||||
};
|
||||
|
||||
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 alpha = component_parser.parse_modern_alpha(arguments)?;
|
||||
let (c1, c2, c3, alpha) = parse_components(
|
||||
&color_parser,
|
||||
arguments,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
ColorParser::parse_number_or_percentage,
|
||||
)?;
|
||||
|
||||
Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha))
|
||||
}
|
||||
|
||||
type ComponentParseResult<'i, R1, R2, R3> = Result<
|
||||
(
|
||||
ColorComponent<R1>,
|
||||
ColorComponent<R2>,
|
||||
ColorComponent<R3>,
|
||||
ColorComponent<NumberOrPercentage>,
|
||||
),
|
||||
ParseError<'i>,
|
||||
>;
|
||||
|
||||
/// Parse the color components and alpha with the modern [color-4] syntax.
|
||||
pub fn parse_components<'a, 'b: 'a, 'i, 't, F1, F2, F3, R1, R2, R3>(
|
||||
color_parser: &ColorParser<'a, 'b>,
|
||||
input: &mut Parser<'i, 't>,
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
f3: F3,
|
||||
) -> ComponentParseResult<'i, R1, R2, R3>
|
||||
where
|
||||
F1: FnOnce(
|
||||
&ColorParser<'a, 'b>,
|
||||
&mut Parser<'i, 't>,
|
||||
bool,
|
||||
) -> Result<ColorComponent<R1>, ParseError<'i>>,
|
||||
F2: FnOnce(
|
||||
&ColorParser<'a, 'b>,
|
||||
&mut Parser<'i, 't>,
|
||||
bool,
|
||||
) -> Result<ColorComponent<R2>, ParseError<'i>>,
|
||||
F3: FnOnce(
|
||||
&ColorParser<'a, 'b>,
|
||||
&mut Parser<'i, 't>,
|
||||
bool,
|
||||
) -> Result<ColorComponent<R3>, ParseError<'i>>,
|
||||
{
|
||||
let r1 = f1(color_parser, input, true)?;
|
||||
let r2 = f2(color_parser, input, true)?;
|
||||
let r3 = f3(color_parser, input, true)?;
|
||||
|
||||
let alpha = parse_modern_alpha(color_parser, input)?;
|
||||
|
||||
Ok((r1, r2, r3, alpha))
|
||||
}
|
||||
|
||||
/// Either a number or a percentage.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum NumberOrPercentage {
|
||||
|
|
@ -497,14 +632,15 @@ impl ColorComponentType for f32 {
|
|||
}
|
||||
|
||||
/// Used to parse the components of a color.
|
||||
pub struct ComponentParser<'a, 'b: 'a> {
|
||||
#[derive(Clone)]
|
||||
pub struct ColorParser<'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>,
|
||||
/// A parsed origin color if one is available.
|
||||
pub origin_color: Option<&'a AbsoluteColor>,
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> ComponentParser<'a, 'b> {
|
||||
impl<'a, 'b: 'a> ColorParser<'a, 'b> {
|
||||
/// Create a new [ColorParser] with the given context.
|
||||
pub fn new(context: &'a ParserContext<'b>) -> Self {
|
||||
Self {
|
||||
|
|
@ -519,7 +655,7 @@ impl<'a, 'b: 'a> ComponentParser<'a, 'b> {
|
|||
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())
|
||||
ColorComponent::parse(self.context, input, allow_none, self.origin_color)
|
||||
}
|
||||
|
||||
/// Parse a `<percentage>` value.
|
||||
|
|
@ -538,7 +674,7 @@ impl<'a, 'b: 'a> ComponentParser<'a, 'b> {
|
|||
self.context,
|
||||
input,
|
||||
allow_none,
|
||||
self.origin_color.as_ref(),
|
||||
self.origin_color,
|
||||
)? {
|
||||
ColorComponent::None => ColorComponent::None,
|
||||
ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) => {
|
||||
|
|
@ -555,7 +691,7 @@ impl<'a, 'b: 'a> ComponentParser<'a, 'b> {
|
|||
input: &mut Parser<'i, 't>,
|
||||
allow_none: bool,
|
||||
) -> Result<ColorComponent<f32>, ParseError<'i>> {
|
||||
ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref())
|
||||
ColorComponent::parse(self.context, input, allow_none, self.origin_color)
|
||||
}
|
||||
|
||||
/// Parse a `<number>` or `<percentage>` value.
|
||||
|
|
@ -564,34 +700,77 @@ impl<'a, 'b: 'a> ComponentParser<'a, 'b> {
|
|||
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 {
|
||||
value: OPAQUE,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_modern_alpha<'i, 't>(
|
||||
&self,
|
||||
arguments: &mut Parser<'i, 't>,
|
||||
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
|
||||
if !arguments.is_exhausted() {
|
||||
arguments.expect_delim('/')?;
|
||||
self.parse_number_or_percentage(arguments, true)
|
||||
} else {
|
||||
Ok(ColorComponent::Value(NumberOrPercentage::Number {
|
||||
value: OPAQUE,
|
||||
}))
|
||||
}
|
||||
ColorComponent::parse(self.context, input, allow_none, self.origin_color)
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait is used by the [`ColorParser`] to construct colors of any type.
|
||||
pub trait FromParsedColor {
|
||||
/// Construct a new color from the CSS `currentcolor` keyword.
|
||||
fn from_current_color() -> Self;
|
||||
|
||||
/// Construct a new color from red, green, blue and alpha components.
|
||||
fn from_rgba(
|
||||
red: ColorComponent<u8>,
|
||||
green: ColorComponent<u8>,
|
||||
blue: ColorComponent<u8>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self;
|
||||
|
||||
/// Construct a new color from hue, saturation, lightness and alpha components.
|
||||
fn from_hsl(
|
||||
hue: ColorComponent<NumberOrAngle>,
|
||||
saturation: ColorComponent<NumberOrPercentage>,
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self;
|
||||
|
||||
/// Construct a new color from hue, blackness, whiteness and alpha components.
|
||||
fn from_hwb(
|
||||
hue: ColorComponent<NumberOrAngle>,
|
||||
whiteness: ColorComponent<NumberOrPercentage>,
|
||||
blackness: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self;
|
||||
|
||||
/// Construct a new color from the `lab` notation.
|
||||
fn from_lab(
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
a: ColorComponent<NumberOrPercentage>,
|
||||
b: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self;
|
||||
|
||||
/// Construct a new color from the `lch` notation.
|
||||
fn from_lch(
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
chroma: ColorComponent<NumberOrPercentage>,
|
||||
hue: ColorComponent<NumberOrAngle>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self;
|
||||
|
||||
/// Construct a new color from the `oklab` notation.
|
||||
fn from_oklab(
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
a: ColorComponent<NumberOrPercentage>,
|
||||
b: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self;
|
||||
|
||||
/// Construct a new color from the `oklch` notation.
|
||||
fn from_oklch(
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
chroma: ColorComponent<NumberOrPercentage>,
|
||||
hue: ColorComponent<NumberOrAngle>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self;
|
||||
|
||||
/// Construct a new color with a predefined color space.
|
||||
fn from_color_function(
|
||||
color_space: PredefinedColorSpace,
|
||||
c1: ColorComponent<NumberOrPercentage>,
|
||||
c2: ColorComponent<NumberOrPercentage>,
|
||||
c3: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@
|
|||
//! Specified color values.
|
||||
|
||||
use super::AllowQuirks;
|
||||
use crate::color::mix::ColorInterpolationMethod;
|
||||
use crate::color::{parsing, AbsoluteColor, ColorSpace};
|
||||
use crate::color::component::ColorComponent;
|
||||
use crate::color::convert::normalize_hue;
|
||||
use crate::color::parsing::{
|
||||
self, ColorParser, FromParsedColor, NumberOrAngle, NumberOrPercentage,
|
||||
};
|
||||
use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace};
|
||||
use crate::media_queries::Device;
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
|
||||
|
|
@ -15,7 +19,8 @@ use crate::values::generics::color::{
|
|||
};
|
||||
use crate::values::specified::Percentage;
|
||||
use crate::values::{normalize, CustomIdent};
|
||||
use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser, Token};
|
||||
use cssparser::color::OPAQUE;
|
||||
use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token};
|
||||
use std::fmt::{self, Write};
|
||||
use std::io::Write as IoWrite;
|
||||
use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
|
||||
|
|
@ -424,6 +429,199 @@ impl SystemColor {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> From<ColorComponent<T>> for Option<T> {
|
||||
fn from(value: ColorComponent<T>) -> Self {
|
||||
match value {
|
||||
ColorComponent::None => None,
|
||||
ColorComponent::Value(value) => Some(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorComponent<NumberOrPercentage> {
|
||||
#[inline]
|
||||
fn into_alpha(self) -> Option<f32> {
|
||||
match self {
|
||||
ColorComponent::None => None,
|
||||
ColorComponent::Value(number_or_percentage) => {
|
||||
Some(normalize(number_or_percentage.to_number(1.0)).clamp(0.0, OPAQUE))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromParsedColor for Color {
|
||||
fn from_current_color() -> Self {
|
||||
Color::CurrentColor
|
||||
}
|
||||
|
||||
fn from_rgba(
|
||||
red: ColorComponent<u8>,
|
||||
green: ColorComponent<u8>,
|
||||
blue: ColorComponent<u8>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self {
|
||||
macro_rules! c {
|
||||
($c:expr) => {{
|
||||
match $c {
|
||||
ColorComponent::None => 0u8,
|
||||
ColorComponent::Value(value) => value,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// Legacy rgb() doesn't support "none" alpha values and falls back to 0.
|
||||
let alpha = alpha.into_alpha().unwrap_or(0.0);
|
||||
|
||||
AbsoluteColor::srgb_legacy(c!(red), c!(green), c!(blue), alpha).into()
|
||||
}
|
||||
|
||||
fn from_hsl(
|
||||
hue: ColorComponent<NumberOrAngle>,
|
||||
saturation: ColorComponent<NumberOrPercentage>,
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self {
|
||||
// Percent reference range for S and L: 0% = 0.0, 100% = 100.0
|
||||
const LIGHTNESS_RANGE: f32 = 100.0;
|
||||
const SATURATION_RANGE: f32 = 100.0;
|
||||
|
||||
let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
|
||||
let saturation =
|
||||
saturation.map_value(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE));
|
||||
let lightness =
|
||||
lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE));
|
||||
|
||||
AbsoluteColor::new(
|
||||
ColorSpace::Hsl,
|
||||
hue,
|
||||
saturation,
|
||||
lightness,
|
||||
alpha.into_alpha(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn from_hwb(
|
||||
hue: ColorComponent<NumberOrAngle>,
|
||||
whiteness: ColorComponent<NumberOrPercentage>,
|
||||
blackness: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self {
|
||||
// Percent reference range for W and B: 0% = 0.0, 100% = 100.0
|
||||
const WHITENESS_RANGE: f32 = 100.0;
|
||||
const BLACKNESS_RANGE: f32 = 100.0;
|
||||
|
||||
let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
|
||||
let whiteness =
|
||||
whiteness.map_value(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE));
|
||||
let blackness =
|
||||
blackness.map_value(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE));
|
||||
|
||||
AbsoluteColor::new(
|
||||
ColorSpace::Hwb,
|
||||
hue,
|
||||
whiteness,
|
||||
blackness,
|
||||
alpha.into_alpha(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn from_lab(
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
a: ColorComponent<NumberOrPercentage>,
|
||||
b: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self {
|
||||
// for L: 0% = 0.0, 100% = 100.0
|
||||
// for a and b: -100% = -125, 100% = 125
|
||||
const LIGHTNESS_RANGE: f32 = 100.0;
|
||||
const A_B_RANGE: f32 = 125.0;
|
||||
|
||||
let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
|
||||
let a = a.map_value(|a| a.to_number(A_B_RANGE));
|
||||
let b = b.map_value(|b| b.to_number(A_B_RANGE));
|
||||
|
||||
AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha.into_alpha()).into()
|
||||
}
|
||||
|
||||
fn from_lch(
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
chroma: ColorComponent<NumberOrPercentage>,
|
||||
hue: ColorComponent<NumberOrAngle>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self {
|
||||
// for L: 0% = 0.0, 100% = 100.0
|
||||
// for C: 0% = 0, 100% = 150
|
||||
const LIGHTNESS_RANGE: f32 = 100.0;
|
||||
const CHROMA_RANGE: f32 = 150.0;
|
||||
|
||||
let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
|
||||
let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE));
|
||||
let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
|
||||
|
||||
AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha.into_alpha()).into()
|
||||
}
|
||||
|
||||
fn from_oklab(
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
a: ColorComponent<NumberOrPercentage>,
|
||||
b: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self {
|
||||
// for L: 0% = 0.0, 100% = 1.0
|
||||
// for a and b: -100% = -0.4, 100% = 0.4
|
||||
const LIGHTNESS_RANGE: f32 = 1.0;
|
||||
const A_B_RANGE: f32 = 0.4;
|
||||
|
||||
let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
|
||||
let a = a.map_value(|a| a.to_number(A_B_RANGE));
|
||||
let b = b.map_value(|b| b.to_number(A_B_RANGE));
|
||||
|
||||
AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha.into_alpha()).into()
|
||||
}
|
||||
|
||||
fn from_oklch(
|
||||
lightness: ColorComponent<NumberOrPercentage>,
|
||||
chroma: ColorComponent<NumberOrPercentage>,
|
||||
hue: ColorComponent<NumberOrAngle>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self {
|
||||
// for L: 0% = 0.0, 100% = 1.0
|
||||
// for C: 0% = 0.0 100% = 0.4
|
||||
const LIGHTNESS_RANGE: f32 = 1.0;
|
||||
const CHROMA_RANGE: f32 = 0.4;
|
||||
|
||||
let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
|
||||
let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE));
|
||||
let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
|
||||
|
||||
AbsoluteColor::new(
|
||||
ColorSpace::Oklch,
|
||||
lightness,
|
||||
chroma,
|
||||
hue,
|
||||
alpha.into_alpha(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn from_color_function(
|
||||
color_space: PredefinedColorSpace,
|
||||
c1: ColorComponent<NumberOrPercentage>,
|
||||
c2: ColorComponent<NumberOrPercentage>,
|
||||
c3: ColorComponent<NumberOrPercentage>,
|
||||
alpha: ColorComponent<NumberOrPercentage>,
|
||||
) -> Self {
|
||||
let c1 = c1.map_value(|c| c.to_number(1.0));
|
||||
let c2 = c2.map_value(|c| c.to_number(1.0));
|
||||
let c3 = c3.map_value(|c| c.to_number(1.0));
|
||||
|
||||
AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha.into_alpha()).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to preserve authored colors during parsing. That's useful only if we
|
||||
/// plan to serialize the color back.
|
||||
#[derive(Copy, Clone)]
|
||||
|
|
@ -460,7 +658,8 @@ impl Color {
|
|||
},
|
||||
};
|
||||
|
||||
match input.try_parse(|i| parsing::parse_color_with(context, i)) {
|
||||
let color_parser = ColorParser::new(&context);
|
||||
match input.try_parse(|i| parsing::parse_color_with(&color_parser, i)) {
|
||||
Ok(mut color) => {
|
||||
if let Color::Absolute(ref mut absolute) = color {
|
||||
// Because we can't set the `authored` value at construction time, we have to set it
|
||||
|
|
@ -628,9 +827,12 @@ impl Color {
|
|||
loc: &cssparser::SourceLocation,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
match cssparser::color::parse_hash_color(bytes) {
|
||||
Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
|
||||
r, g, b, a,
|
||||
))),
|
||||
Ok((r, g, b, a)) => Ok(Self::from_rgba(
|
||||
r.into(),
|
||||
g.into(),
|
||||
b.into(),
|
||||
ColorComponent::Value(NumberOrPercentage::Number { value: a }),
|
||||
)),
|
||||
Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue