Bug 1880307 - Color component parsers return typed values. r=layout-reviewers,emilio

The parsing functions now return typed values like NumberOrPercentage or
NumberOrAngle and only once they are converted to AbsoluteColor (which
will change in the near future to accommodate RCS) are they converted to
f32 values.

Depends on D203465

Differential Revision: https://phabricator.services.mozilla.com/D203410
This commit is contained in:
Tiaan Louw 2024-03-13 12:42:32 +00:00
Родитель d0bd10bb53
Коммит f9672884e8
4 изменённых файлов: 451 добавлений и 298 удалений

Просмотреть файл

@ -30,7 +30,6 @@ impl<ValueType> ColorComponent<ValueType> {
Self::Value(value) => ColorComponent::Value(f(value)),
}
}
/// Return the component as its value.
pub fn into_value(self) -> ValueType {
match self {

Просмотреть файл

@ -8,16 +8,20 @@
//! Relative colors, color-mix, system colors, and other such things require better calc() support
//! and integration.
use crate::color::convert::normalize_hue;
use crate::values::normalize;
use crate::color::component::ColorComponent;
use cssparser::color::{
clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE,
};
use cssparser::{match_ignore_ascii_case, CowRcStr, Parser, Token};
use std::str::FromStr;
use style_traits::ParseError;
use style_traits::{ParseError, StyleParseErrorKind};
use super::component::ColorComponent;
impl From<u8> for ColorComponent<u8> {
#[inline]
fn from(value: u8) -> Self {
ColorComponent::Value(value)
}
}
/// Return the named color with the given name.
///
@ -29,13 +33,23 @@ pub fn parse_color_keyword<Output>(ident: &str) -> Result<Output, ()>
where
Output: FromParsedColor,
{
Ok(match_ignore_ascii_case! { ident ,
"transparent" => Output::from_rgba(0, 0, 0, 0.0),
Ok(match_ignore_ascii_case! { ident,
"transparent" => Output::from_rgba(
0u8.into(),
0u8.into(),
0u8.into(),
ColorComponent::Value(NumberOrPercentage::Number { value: 0.0 }),
),
"currentcolor" => Output::from_current_color(),
_ => {
let (r, g, b) = cssparser::color::parse_named_color(ident)?;
Output::from_rgba(r, g, b, OPAQUE)
}
Output::from_rgba(
r.into(),
g.into(),
b.into(),
ColorComponent::Value(NumberOrPercentage::Number { value: OPAQUE }),
)
},
})
}
@ -51,9 +65,15 @@ where
let location = input.current_source_location();
let token = input.next()?;
match *token {
Token::Hash(ref value) | Token::IDHash(ref value) => {
parse_hash_color(value.as_bytes()).map(|(r, g, b, a)| P::Output::from_rgba(r, g, b, a))
},
Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes())
.map(|(r, g, b, a)| {
P::Output::from_rgba(
r.into(),
g.into(),
b.into(),
ColorComponent::Value(NumberOrPercentage::Number { value: a }),
)
}),
Token::Ident(ref value) => parse_color_keyword(value),
Token::Function(ref name) => {
let name = name.clone();
@ -78,29 +98,13 @@ where
{
let color = match_ignore_ascii_case! { &name,
"rgb" | "rgba" => parse_rgb(color_parser, arguments),
"hsl" | "hsla" => parse_hsl(color_parser, arguments),
"hwb" => parse_hwb(color_parser, arguments),
// for L: 0% = 0.0, 100% = 100.0
// for a and b: -100% = -125, 100% = 125
"lab" => parse_lab_like(color_parser, arguments, 100.0, 125.0, P::Output::from_lab),
// for L: 0% = 0.0, 100% = 100.0
// for C: 0% = 0, 100% = 150
"lch" => parse_lch_like(color_parser, arguments, 100.0, 150.0, P::Output::from_lch),
// for L: 0% = 0.0, 100% = 1.0
// for a and b: -100% = -0.4, 100% = 0.4
"oklab" => parse_lab_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklab),
// for L: 0% = 0.0, 100% = 1.0
// for C: 0% = 0.0 100% = 0.4
"oklch" => parse_lch_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklch),
"lab" => parse_lab_like(color_parser, arguments, P::Output::from_lab),
"lch" => parse_lch_like(color_parser, arguments, P::Output::from_lch),
"oklab" => parse_lab_like(color_parser, arguments, P::Output::from_oklab),
"oklch" => parse_lch_like(color_parser, arguments, P::Output::from_oklch),
"color" => parse_color_with_color_space(color_parser, arguments),
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
}?;
@ -109,50 +113,47 @@ where
Ok(color)
}
/// Parse the alpha component by itself from either number or percentage,
/// clipping the result to [0.0..1.0].
#[inline]
fn parse_alpha_component<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
) -> Result<f32, ParseError<'i>>
where
P: ColorParser<'i>,
{
// Percent reference range for alpha: 0% = 0.0, 100% = 1.0
let alpha = color_parser
.parse_number_or_percentage(arguments)?
.to_number(1.0);
Ok(normalize(alpha).clamp(0.0, OPAQUE))
}
fn parse_legacy_alpha<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
) -> Result<f32, ParseError<'i>>
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>
where
P: ColorParser<'i>,
{
Ok(if !arguments.is_exhausted() {
if !arguments.is_exhausted() {
arguments.expect_comma()?;
parse_alpha_component(color_parser, arguments)?
color_parser.parse_number_or_percentage(arguments, false)
} else {
OPAQUE
})
Ok(ColorComponent::Value(NumberOrPercentage::Number {
value: OPAQUE,
}))
}
}
fn parse_modern_alpha<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorComponent<f32>, ParseError<'i>>
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>
where
P: ColorParser<'i>,
{
if !arguments.is_exhausted() {
arguments.expect_delim('/')?;
parse_none_or(arguments, |p| parse_alpha_component(color_parser, p))
color_parser.parse_number_or_percentage(arguments, true)
} else {
Ok(ColorComponent::Value(OPAQUE))
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, ()> {
Ok(match self {
Self::Value(NumberOrPercentage::Percentage { .. }) => true,
_ => false,
})
}
}
@ -164,7 +165,9 @@ fn parse_rgb<'i, 't, P>(
where
P: ColorParser<'i>,
{
let maybe_red = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?;
let location = arguments.current_source_location();
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.
@ -172,47 +175,44 @@ where
!maybe_red.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok();
let (red, green, blue, alpha) = if is_legacy_syntax {
let (red, green, blue) = match maybe_red.into_value() {
NumberOrPercentage::Number { value } => {
let red = clamp_floor_256_f32(value);
let green = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
let Ok(is_percentage) = maybe_red.is_percentage() else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
};
let (red, green, blue) = if is_percentage {
let red = maybe_red.map_value(|v| clamp_unit_f32(v.to_number(1.0)));
let green = color_parser
.parse_percentage(arguments, false)?
.map_value(clamp_unit_f32);
arguments.expect_comma()?;
let blue = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
let blue = color_parser
.parse_percentage(arguments, false)?
.map_value(clamp_unit_f32);
(red, green, blue)
},
NumberOrPercentage::Percentage { unit_value } => {
let red = clamp_unit_f32(unit_value);
let green = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
} else {
let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
let green = color_parser
.parse_number(arguments, false)?
.map_value(clamp_floor_256_f32);
arguments.expect_comma()?;
let blue = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
let blue = color_parser
.parse_number(arguments, false)?
.map_value(clamp_floor_256_f32);
(red, green, blue)
},
};
let alpha = parse_legacy_alpha(color_parser, arguments)?;
(red, green, blue, alpha)
} else {
#[inline]
fn get_component_value(c: ColorComponent<NumberOrPercentage>) -> u8 {
c.map_value(|c| match c {
NumberOrPercentage::Number { value } => clamp_floor_256_f32(value),
NumberOrPercentage::Percentage { unit_value } => clamp_unit_f32(unit_value),
})
.into_value_or(0)
}
let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
let green = color_parser
.parse_number_or_percentage(arguments, true)?
.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
let blue = color_parser
.parse_number_or_percentage(arguments, true)?
.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
let red = get_component_value(maybe_red);
let green = get_component_value(parse_none_or(arguments, |p| {
color_parser.parse_number_or_percentage(p)
})?);
let blue = get_component_value(parse_none_or(arguments, |p| {
color_parser.parse_number_or_percentage(p)
})?);
let alpha = parse_modern_alpha(color_parser, arguments)?.into_value_or(0.0);
let alpha = parse_modern_alpha(color_parser, arguments)?;
(red, green, blue, alpha)
};
@ -231,39 +231,35 @@ fn parse_hsl<'i, 't, P>(
where
P: ColorParser<'i>,
{
// 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 maybe_hue = parse_none_or(arguments, |p| color_parser.parse_number_or_angle(p))?;
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.
let is_legacy_syntax =
!maybe_hue.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok();
let is_legacy_syntax = !hue.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok();
let saturation: ColorComponent<f32>;
let lightness: ColorComponent<f32>;
let alpha = if is_legacy_syntax {
saturation =
ColorComponent::Value(color_parser.parse_percentage(arguments)? * SATURATION_RANGE);
let (saturation, lightness, alpha) = if is_legacy_syntax {
let saturation = color_parser
.parse_percentage(arguments, false)?
.map_value(|unit_value| NumberOrPercentage::Percentage { unit_value });
arguments.expect_comma()?;
lightness =
ColorComponent::Value(color_parser.parse_percentage(arguments)? * LIGHTNESS_RANGE);
ColorComponent::Value(parse_legacy_alpha(color_parser, arguments)?)
let lightness = color_parser
.parse_percentage(arguments, false)?
.map_value(|unit_value| NumberOrPercentage::Percentage { unit_value });
(
saturation,
lightness,
parse_legacy_alpha(color_parser, arguments)?,
)
} else {
saturation = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?
.map_value(|v| v.to_number(SATURATION_RANGE));
lightness = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?
.map_value(|v| v.to_number(LIGHTNESS_RANGE));
parse_modern_alpha(color_parser, arguments)?
let saturation = color_parser.parse_number_or_percentage(arguments, true)?;
let lightness = color_parser.parse_number_or_percentage(arguments, true)?;
(
saturation,
lightness,
parse_modern_alpha(color_parser, arguments)?,
)
};
let hue = maybe_hue.map_value(|h| normalize_hue(h.degrees()));
let saturation = saturation.map_value(|s| s.clamp(0.0, SATURATION_RANGE));
let lightness = lightness.map_value(|l| l.clamp(0.0, LIGHTNESS_RANGE));
Ok(P::Output::from_hsl(hue, saturation, lightness, alpha))
}
@ -278,10 +274,6 @@ fn parse_hwb<'i, 't, P>(
where
P: ColorParser<'i>,
{
// 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, whiteness, blackness, alpha) = parse_components(
color_parser,
arguments,
@ -290,29 +282,21 @@ where
P::parse_number_or_percentage,
)?;
let hue = hue.map_value(|h| normalize_hue(h.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));
Ok(P::Output::from_hwb(hue, whiteness, blackness, alpha))
}
type IntoColorFn<Output> = fn(
l: ColorComponent<f32>,
a: ColorComponent<f32>,
b: ColorComponent<f32>,
alpha: ColorComponent<f32>,
type IntoLabFn<Output> = fn(
l: ColorComponent<NumberOrPercentage>,
a: ColorComponent<NumberOrPercentage>,
b: ColorComponent<NumberOrPercentage>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Output;
#[inline]
fn parse_lab_like<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
lightness_range: f32,
a_b_range: f32,
into_color: IntoColorFn<P::Output>,
into_color: IntoLabFn<P::Output>,
) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
@ -325,20 +309,21 @@ where
P::parse_number_or_percentage,
)?;
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));
Ok(into_color(lightness, a, b, alpha))
}
type IntoLchFn<Output> = fn(
l: ColorComponent<NumberOrPercentage>,
a: ColorComponent<NumberOrPercentage>,
b: ColorComponent<NumberOrAngle>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Output;
#[inline]
fn parse_lch_like<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
lightness_range: f32,
chroma_range: f32,
into_color: IntoColorFn<P::Output>,
into_color: IntoLchFn<P::Output>,
) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
@ -351,10 +336,6 @@ where
P::parse_number_or_angle,
)?;
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(|h| normalize_hue(h.degrees()));
Ok(into_color(lightness, chroma, hue, alpha))
}
@ -383,10 +364,6 @@ where
P::parse_number_or_percentage,
)?;
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));
Ok(P::Output::from_color_function(
color_space,
c1,
@ -401,7 +378,7 @@ type ComponentParseResult<'i, R1, R2, R3> = Result<
ColorComponent<R1>,
ColorComponent<R2>,
ColorComponent<R3>,
ColorComponent<f32>,
ColorComponent<NumberOrPercentage>,
),
ParseError<'i>,
>;
@ -416,32 +393,19 @@ pub fn parse_components<'i, 't, P, F1, F2, F3, R1, R2, R3>(
) -> ComponentParseResult<'i, R1, R2, R3>
where
P: ColorParser<'i>,
F1: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R1, ParseError<'i>>,
F2: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R2, ParseError<'i>>,
F3: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R3, ParseError<'i>>,
F1: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R1>, ParseError<'i>>,
F2: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R2>, ParseError<'i>>,
F3: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R3>, ParseError<'i>>,
{
let r1 = parse_none_or(input, |p| f1(color_parser, p))?;
let r2 = parse_none_or(input, |p| f2(color_parser, p))?;
let r3 = parse_none_or(input, |p| f3(color_parser, p))?;
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))
}
fn parse_none_or<'i, 't, F, T, E>(
input: &mut Parser<'i, 't>,
thing: F,
) -> Result<ColorComponent<T>, E>
where
F: FnOnce(&mut Parser<'i, 't>) -> Result<T, E>,
{
match input.try_parse(|p| p.expect_ident_matching("none")) {
Ok(_) => Ok(ColorComponent::None),
Err(_) => Ok(ColorComponent::Value(thing(input)?)),
}
}
/// Either a number or a percentage.
pub enum NumberOrPercentage {
/// `<number>`.
@ -507,21 +471,31 @@ pub trait ColorParser<'i> {
fn parse_number_or_angle<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<NumberOrAngle, ParseError<'i>>;
allow_none: bool,
) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>>;
/// Parse a `<percentage>` value.
///
/// Returns the result in a number from 0.0 to 1.0.
fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>>;
fn parse_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<f32>, ParseError<'i>>;
/// Parse a `<number>` value.
fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>>;
fn parse_number<'t>(
&self,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<f32>, ParseError<'i>>;
/// Parse a `<number>` value or a `<percentage>` value.
fn parse_number_or_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<NumberOrPercentage, ParseError<'i>>;
allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>;
}
/// This trait is used by the [`ColorParser`] to construct colors of any type.
@ -530,62 +504,67 @@ pub trait FromParsedColor {
fn from_current_color() -> Self;
/// Construct a new color from red, green, blue and alpha components.
fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self;
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<f32>,
saturation: ColorComponent<f32>,
lightness: ColorComponent<f32>,
alpha: ColorComponent<f32>,
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<f32>,
whiteness: ColorComponent<f32>,
blackness: ColorComponent<f32>,
alpha: ColorComponent<f32>,
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<f32>,
a: ColorComponent<f32>,
b: ColorComponent<f32>,
alpha: ColorComponent<f32>,
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<f32>,
chroma: ColorComponent<f32>,
hue: ColorComponent<f32>,
alpha: ColorComponent<f32>,
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<f32>,
a: ColorComponent<f32>,
b: ColorComponent<f32>,
alpha: ColorComponent<f32>,
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<f32>,
chroma: ColorComponent<f32>,
hue: ColorComponent<f32>,
alpha: ColorComponent<f32>,
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<f32>,
c2: ColorComponent<f32>,
c3: ColorComponent<f32>,
alpha: ColorComponent<f32>,
c1: ColorComponent<NumberOrPercentage>,
c2: ColorComponent<NumberOrPercentage>,
c3: ColorComponent<NumberOrPercentage>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self;
}

Просмотреть файл

@ -6,7 +6,6 @@
//!
//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
use crate::color::parsing::{NumberOrAngle, NumberOrPercentage};
use crate::parser::ParserContext;
use crate::values::generics::calc::{
self as generic, CalcNodeLeaf, CalcUnits, MinMaxOp, ModRemOp, PositivePercentageBasis,
@ -486,7 +485,7 @@ impl CalcNode {
/// Parse a top-level `calc` expression, with all nested sub-expressions.
///
/// This is in charge of parsing, for example, `2 + 3 * 100%`.
fn parse<'i, 't>(
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
function: MathFunction,
@ -1055,41 +1054,4 @@ impl CalcNode {
.to_resolution()
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
/// Convenience parsing function for `<number>` or `<percentage>`.
pub fn parse_number_or_percentage<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
function: MathFunction,
) -> Result<NumberOrPercentage, ParseError<'i>> {
let node = Self::parse(context, input, function, CalcUnits::PERCENTAGE)?;
if let Ok(value) = node.to_number() {
return Ok(NumberOrPercentage::Number { value });
}
match node.to_percentage() {
Ok(unit_value) => Ok(NumberOrPercentage::Percentage { unit_value }),
Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}
/// Convenience parsing function for `<number>` or `<angle>`.
pub fn parse_number_or_angle<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
function: MathFunction,
) -> Result<NumberOrAngle, ParseError<'i>> {
let node = Self::parse(context, input, function, CalcUnits::ANGLE)?;
if let Ok(angle) = node.to_angle() {
let degrees = angle.degrees();
return Ok(NumberOrAngle::Angle { degrees });
}
match node.to_number() {
Ok(value) => Ok(NumberOrAngle::Number { value }),
Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}
}

Просмотреть файл

@ -6,17 +6,20 @@
use super::AllowQuirks;
use crate::color::component::ColorComponent;
use crate::color::convert::normalize_hue;
use crate::color::parsing::{self, FromParsedColor, NumberOrAngle, NumberOrPercentage};
use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace};
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
use crate::values::generics::calc::CalcUnits;
use crate::values::generics::color::{
ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorOrAuto,
};
use crate::values::specified::calc::CalcNode;
use crate::values::specified::calc::{CalcNode, Leaf};
use crate::values::specified::Percentage;
use crate::values::{normalize, CustomIdent};
use cssparser::color::OPAQUE;
use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token};
use itoa;
use std::fmt::{self, Write};
@ -427,93 +430,217 @@ impl SystemColor {
}
}
impl<T> From<ColorComponent<T>> for Option<T> {
fn from(value: ColorComponent<T>) -> Self {
match value {
ColorComponent::None => None,
ColorComponent::Value(value) => Some(value),
}
}
}
impl ColorComponent<NumberOrPercentage> {
#[inline]
fn into_alpha(self) -> Option<f32> {
match self {
ColorComponent::None => None,
ColorComponent::Value(number_or_percentage) => {
Some(normalize(number_or_percentage.to_number(1.0)).clamp(0.0, OPAQUE))
},
}
}
}
impl FromParsedColor for Color {
fn from_current_color() -> Self {
Color::CurrentColor
}
fn from_rgba(r: u8, g: u8, b: u8, a: f32) -> Self {
AbsoluteColor::srgb_legacy(r, g, b, a).into()
fn from_rgba(
red: ColorComponent<u8>,
green: ColorComponent<u8>,
blue: ColorComponent<u8>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
macro_rules! c {
($c:expr) => {{
match $c {
ColorComponent::None => 0u8,
ColorComponent::Value(value) => value,
}
}};
}
// Legacy rgb() doesn't support "none" alpha values and falls back to 0.
let alpha = alpha.into_alpha().unwrap_or(0.0);
AbsoluteColor::srgb_legacy(c!(red), c!(green), c!(blue), alpha).into()
}
fn from_hsl(
hue: ColorComponent<f32>,
saturation: ColorComponent<f32>,
lightness: ColorComponent<f32>,
alpha: ColorComponent<f32>,
hue: ColorComponent<NumberOrAngle>,
saturation: ColorComponent<NumberOrPercentage>,
lightness: ColorComponent<NumberOrPercentage>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
AbsoluteColor::new(ColorSpace::Hsl, hue, saturation, lightness, alpha).into()
// Percent reference range for S and L: 0% = 0.0, 100% = 100.0
const LIGHTNESS_RANGE: f32 = 100.0;
const SATURATION_RANGE: f32 = 100.0;
let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
let saturation =
saturation.map_value(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE));
let lightness =
lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE));
AbsoluteColor::new(
ColorSpace::Hsl,
hue,
saturation,
lightness,
alpha.into_alpha(),
)
.into()
}
fn from_hwb(
hue: ColorComponent<f32>,
whiteness: ColorComponent<f32>,
blackness: ColorComponent<f32>,
alpha: ColorComponent<f32>,
hue: ColorComponent<NumberOrAngle>,
whiteness: ColorComponent<NumberOrPercentage>,
blackness: ColorComponent<NumberOrPercentage>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
AbsoluteColor::new(ColorSpace::Hwb, hue, whiteness, blackness, alpha).into()
// Percent reference range for W and B: 0% = 0.0, 100% = 100.0
const WHITENESS_RANGE: f32 = 100.0;
const BLACKNESS_RANGE: f32 = 100.0;
let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
let whiteness =
whiteness.map_value(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE));
let blackness =
blackness.map_value(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE));
AbsoluteColor::new(
ColorSpace::Hwb,
hue,
whiteness,
blackness,
alpha.into_alpha(),
)
.into()
}
fn from_lab(
lightness: ColorComponent<f32>,
a: ColorComponent<f32>,
b: ColorComponent<f32>,
alpha: ColorComponent<f32>,
lightness: ColorComponent<NumberOrPercentage>,
a: ColorComponent<NumberOrPercentage>,
b: ColorComponent<NumberOrPercentage>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha).into()
// for L: 0% = 0.0, 100% = 100.0
// for a and b: -100% = -125, 100% = 125
const LIGHTNESS_RANGE: f32 = 100.0;
const A_B_RANGE: f32 = 125.0;
let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
let a = a.map_value(|a| a.to_number(A_B_RANGE));
let b = b.map_value(|b| b.to_number(A_B_RANGE));
AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha.into_alpha()).into()
}
fn from_lch(
lightness: ColorComponent<f32>,
chroma: ColorComponent<f32>,
hue: ColorComponent<f32>,
alpha: ColorComponent<f32>,
lightness: ColorComponent<NumberOrPercentage>,
chroma: ColorComponent<NumberOrPercentage>,
hue: ColorComponent<NumberOrAngle>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha).into()
// for L: 0% = 0.0, 100% = 100.0
// for C: 0% = 0, 100% = 150
const LIGHTNESS_RANGE: f32 = 100.0;
const CHROMA_RANGE: f32 = 150.0;
let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE));
let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha.into_alpha()).into()
}
fn from_oklab(
lightness: ColorComponent<f32>,
a: ColorComponent<f32>,
b: ColorComponent<f32>,
alpha: ColorComponent<f32>,
lightness: ColorComponent<NumberOrPercentage>,
a: ColorComponent<NumberOrPercentage>,
b: ColorComponent<NumberOrPercentage>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha).into()
// for L: 0% = 0.0, 100% = 1.0
// for a and b: -100% = -0.4, 100% = 0.4
const LIGHTNESS_RANGE: f32 = 1.0;
const A_B_RANGE: f32 = 0.4;
let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
let a = a.map_value(|a| a.to_number(A_B_RANGE));
let b = b.map_value(|b| b.to_number(A_B_RANGE));
AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha.into_alpha()).into()
}
fn from_oklch(
lightness: ColorComponent<f32>,
chroma: ColorComponent<f32>,
hue: ColorComponent<f32>,
alpha: ColorComponent<f32>,
lightness: ColorComponent<NumberOrPercentage>,
chroma: ColorComponent<NumberOrPercentage>,
hue: ColorComponent<NumberOrAngle>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
AbsoluteColor::new(ColorSpace::Oklch, lightness, chroma, hue, alpha).into()
// for L: 0% = 0.0, 100% = 1.0
// for C: 0% = 0.0 100% = 0.4
const LIGHTNESS_RANGE: f32 = 1.0;
const CHROMA_RANGE: f32 = 0.4;
let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE));
let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
AbsoluteColor::new(
ColorSpace::Oklch,
lightness,
chroma,
hue,
alpha.into_alpha(),
)
.into()
}
fn from_color_function(
color_space: PredefinedColorSpace,
c1: ColorComponent<f32>,
c2: ColorComponent<f32>,
c3: ColorComponent<f32>,
alpha: ColorComponent<f32>,
c1: ColorComponent<NumberOrPercentage>,
c2: ColorComponent<NumberOrPercentage>,
c3: ColorComponent<NumberOrPercentage>,
alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha).into()
let c1 = c1.map_value(|c| c.to_number(1.0));
let c2 = c2.map_value(|c| c.to_number(1.0));
let c3 = c3.map_value(|c| c.to_number(1.0));
AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha.into_alpha()).into()
}
}
struct ColorParser<'a, 'b: 'a>(&'a ParserContext<'b>);
impl<'a, 'b: 'a, 'i: 'a> parsing::ColorParser<'i> for ColorParser<'a, 'b> {
type Output = Color;
fn parse_number_or_angle<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<NumberOrAngle, ParseError<'i>> {
allow_none: bool,
) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> {
use crate::values::specified::Angle;
let location = input.current_source_location();
let token = input.next()?.clone();
match token {
Ok(match token {
Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
ColorComponent::None
},
Token::Dimension {
value, ref unit, ..
} => {
@ -524,44 +651,125 @@ impl<'a, 'b: 'a, 'i: 'a> parsing::ColorParser<'i> for ColorParser<'a, 'b> {
Err(()) => return Err(location.new_unexpected_token_error(token.clone())),
};
Ok(NumberOrAngle::Angle { degrees })
ColorComponent::Value(NumberOrAngle::Angle { degrees })
},
Token::Number { value, .. } => Ok(NumberOrAngle::Number { value }),
Token::Number { value, .. } => ColorComponent::Value(NumberOrAngle::Number { value }),
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
CalcNode::parse_number_or_angle(self.0, input, function)
let node = CalcNode::parse(self.0, input, function, CalcUnits::ANGLE)?;
// If we can resolve the calc node, then use the value.
match node.resolve() {
Ok(Leaf::Number(value)) => {
ColorComponent::Value(NumberOrAngle::Number { value })
},
Ok(Leaf::Angle(angle)) => ColorComponent::Value(NumberOrAngle::Angle {
degrees: angle.degrees(),
}),
_ => {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
},
}
},
t => return Err(location.new_unexpected_token_error(t)),
}
})
}
fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
Ok(Percentage::parse(self.0, input)?.get())
fn parse_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<f32>, ParseError<'i>> {
let location = input.current_source_location();
Ok(match *input.next()? {
Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
ColorComponent::None
},
Token::Percentage { unit_value, .. } => ColorComponent::Value(unit_value),
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
let node = CalcNode::parse(self.0, input, function, CalcUnits::PERCENTAGE)?;
// If we can resolve the calc node, then use the value.
let Ok(resolved_leaf) = node.resolve() else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
};
if let Leaf::Percentage(value) = resolved_leaf {
ColorComponent::Value(value)
} else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
},
ref t => return Err(location.new_unexpected_token_error(t.clone())),
})
}
fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
use crate::values::specified::Number;
fn parse_number<'t>(
&self,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<f32>, ParseError<'i>> {
let location = input.current_source_location();
Ok(Number::parse(self.0, input)?.get())
Ok(match *input.next()? {
Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
ColorComponent::None
},
Token::Number { value, .. } => ColorComponent::Value(value),
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
let node = CalcNode::parse(self.0, input, function, CalcUnits::empty())?;
// If we can resolve the calc node, then use the value.
let Ok(resolved_leaf) = node.resolve() else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
};
if let Leaf::Number(value) = resolved_leaf {
ColorComponent::Value(value)
} else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
},
ref t => return Err(location.new_unexpected_token_error(t.clone())),
})
}
fn parse_number_or_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<NumberOrPercentage, ParseError<'i>> {
allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }),
Ok(match *input.next()? {
Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
ColorComponent::None
},
Token::Number { value, .. } => {
ColorComponent::Value(NumberOrPercentage::Number { value })
},
Token::Percentage { unit_value, .. } => {
Ok(NumberOrPercentage::Percentage { unit_value })
ColorComponent::Value(NumberOrPercentage::Percentage { unit_value })
},
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
CalcNode::parse_number_or_percentage(self.0, input, function)
let node = CalcNode::parse(self.0, input, function, CalcUnits::PERCENTAGE)?;
// If we can resolve the calc node, then use the value.
let Ok(resolved_leaf) = node.resolve() else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
};
if let Leaf::Percentage(unit_value) = resolved_leaf {
ColorComponent::Value(NumberOrPercentage::Percentage { unit_value })
} else if let Leaf::Number(value) = resolved_leaf {
ColorComponent::Value(NumberOrPercentage::Number { value })
} else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
},
ref t => return Err(location.new_unexpected_token_error(t.clone())),
}
})
}
}
@ -770,7 +978,12 @@ impl Color {
loc: &cssparser::SourceLocation,
) -> Result<Self, ParseError<'i>> {
match cssparser::color::parse_hash_color(bytes) {
Ok((r, g, b, a)) => Ok(Self::from_rgba(r, g, b, a)),
Ok((r, g, b, a)) => Ok(Self::from_rgba(
r.into(),
g.into(),
b.into(),
ColorComponent::Value(NumberOrPercentage::Number { value: a }),
)),
Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}