summaryrefslogtreecommitdiffstats
path: root/servo/components/style/color/parsing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/color/parsing.rs')
-rw-r--r--servo/components/style/color/parsing.rs1092
1 files changed, 208 insertions, 884 deletions
diff --git a/servo/components/style/color/parsing.rs b/servo/components/style/color/parsing.rs
index f60b44c5b6..1ae32c4ad7 100644
--- a/servo/components/style/color/parsing.rs
+++ b/servo/components/style/color/parsing.rs
@@ -8,21 +8,20 @@
//! Relative colors, color-mix, system colors, and other such things require better calc() support
//! and integration.
-use super::{
- convert::{hsl_to_rgb, hwb_to_rgb, normalize_hue},
- ColorComponents,
-};
-use crate::values::normalize;
+use crate::color::component::ColorComponent;
use cssparser::color::{
- clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, serialize_color_alpha,
- PredefinedColorSpace, OPAQUE,
+ clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE,
};
-use cssparser::{match_ignore_ascii_case, CowRcStr, ParseError, Parser, ToCss, Token};
-#[cfg(feature = "serde")]
-use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use std::f32::consts::PI;
-use std::fmt;
+use cssparser::{match_ignore_ascii_case, CowRcStr, Parser, Token};
use std::str::FromStr;
+use style_traits::{ParseError, StyleParseErrorKind};
+
+impl From<u8> for ColorComponent<u8> {
+ #[inline]
+ fn from(value: u8) -> Self {
+ ColorComponent::Value(value)
+ }
+}
/// Return the named color with the given name.
///
@@ -34,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 }),
+ )
+ },
})
}
@@ -49,16 +58,22 @@ where
pub fn parse_color_with<'i, 't, P>(
color_parser: &P,
input: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
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();
@@ -77,35 +92,19 @@ fn parse_color_function<'i, 't, P>(
color_parser: &P,
name: CowRcStr<'i>,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
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))),
}?;
@@ -114,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, P::Error>>
-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, P::Error>>
+) -> 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<Option<f32>, ParseError<'i, P::Error>>
+) -> 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(Some(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,
+ })
}
}
@@ -165,58 +161,58 @@ where
fn parse_rgb<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
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.
- let is_legacy_syntax = maybe_red.is_some() && arguments.try_parse(|p| p.expect_comma()).is_ok();
+ let is_legacy_syntax =
+ !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.unwrap() {
- NumberOrPercentage::Number { value } => {
- let red = clamp_floor_256_f32(value);
- let green = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
- arguments.expect_comma()?;
- let blue = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
- (red, green, blue)
- },
- NumberOrPercentage::Percentage { unit_value } => {
- let red = clamp_unit_f32(unit_value);
- let green = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
- arguments.expect_comma()?;
- let blue = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
- (red, green, blue)
- },
+ 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 = 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 = color_parser
+ .parse_number(arguments, false)?
+ .map_value(clamp_floor_256_f32);
+ arguments.expect_comma()?;
+ 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: Option<NumberOrPercentage>) -> u8 {
- c.map(|c| match c {
- NumberOrPercentage::Number { value } => clamp_floor_256_f32(value),
- NumberOrPercentage::Percentage { unit_value } => clamp_unit_f32(unit_value),
- })
- .unwrap_or(0)
- }
-
- let red = get_component_value(maybe_red);
+ 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 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)?.unwrap_or(0.0);
+ let alpha = parse_modern_alpha(color_parser, arguments)?;
(red, green, blue, alpha)
};
@@ -231,40 +227,39 @@ where
fn parse_hsl<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
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_angle_or_number(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_some() && arguments.try_parse(|p| p.expect_comma()).is_ok();
-
- let saturation: Option<f32>;
- let lightness: Option<f32>;
+ let is_legacy_syntax = !hue.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok();
- let alpha = if is_legacy_syntax {
- saturation = Some(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 = Some(color_parser.parse_percentage(arguments)? * LIGHTNESS_RANGE);
- Some(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(|v| v.to_number(SATURATION_RANGE));
- lightness = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?
- .map(|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(|h| normalize_hue(h.degrees()));
- let saturation = saturation.map(|s| s.clamp(0.0, SATURATION_RANGE));
- let lightness = lightness.map(|l| l.clamp(0.0, LIGHTNESS_RANGE));
-
Ok(P::Output::from_hsl(hue, saturation, lightness, alpha))
}
@@ -275,40 +270,34 @@ where
fn parse_hwb<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
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,
- P::parse_angle_or_number,
+ P::parse_number_or_angle,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
)?;
- let hue = hue.map(|h| normalize_hue(h.degrees()));
- let whiteness = whiteness.map(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE));
- let blackness = blackness.map(|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: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>) -> Output;
+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>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+ into_color: IntoLabFn<P::Output>,
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
@@ -320,21 +309,22 @@ where
P::parse_number_or_percentage,
)?;
- let lightness = lightness.map(|l| l.to_number(lightness_range));
- let a = a.map(|a| a.to_number(a_b_range));
- let b = b.map(|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>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+ into_color: IntoLchFn<P::Output>,
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
@@ -343,13 +333,9 @@ where
arguments,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
- P::parse_angle_or_number,
+ P::parse_number_or_angle,
)?;
- let lightness = lightness.map(|l| l.to_number(lightness_range));
- let chroma = chroma.map(|c| c.to_number(chroma_range));
- let hue = hue.map(|h| normalize_hue(h.degrees()));
-
Ok(into_color(lightness, chroma, hue, alpha))
}
@@ -358,7 +344,7 @@ where
fn parse_color_with_color_space<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
@@ -378,10 +364,6 @@ where
P::parse_number_or_percentage,
)?;
- let c1 = c1.map(|c| c.to_number(1.0));
- let c2 = c2.map(|c| c.to_number(1.0));
- let c3 = c3.map(|c| c.to_number(1.0));
-
Ok(P::Output::from_color_function(
color_space,
c1,
@@ -391,8 +373,15 @@ where
))
}
-type ComponentParseResult<'i, R1, R2, R3, Error> =
- Result<(Option<R1>, Option<R2>, Option<R3>, Option<f32>), ParseError<'i, Error>>;
+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<'i, 't, P, F1, F2, F3, R1, R2, R3>(
@@ -401,567 +390,22 @@ pub fn parse_components<'i, 't, P, F1, F2, F3, R1, R2, R3>(
f1: F1,
f2: F2,
f3: F3,
-) -> ComponentParseResult<'i, R1, R2, R3, P::Error>
+) -> ComponentParseResult<'i, R1, R2, R3>
where
P: ColorParser<'i>,
- F1: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R1, ParseError<'i, P::Error>>,
- F2: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R2, ParseError<'i, P::Error>>,
- F3: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R3, ParseError<'i, P::Error>>,
+ 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<Option<T>, E>
-where
- F: FnOnce(&mut Parser<'i, 't>) -> Result<T, E>,
-{
- match input.try_parse(|p| p.expect_ident_matching("none")) {
- Ok(_) => Ok(None),
- Err(_) => Ok(Some(thing(input)?)),
- }
-}
-
-/// A [`ModernComponent`] can serialize to `none`, `nan`, `infinity` and
-/// floating point values.
-struct ModernComponent<'a>(&'a Option<f32>);
-
-impl<'a> ToCss for ModernComponent<'a> {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- if let Some(value) = self.0 {
- if value.is_finite() {
- value.to_css(dest)
- } else if value.is_nan() {
- dest.write_str("calc(NaN)")
- } else {
- debug_assert!(value.is_infinite());
- if value.is_sign_negative() {
- dest.write_str("calc(-infinity)")
- } else {
- dest.write_str("calc(infinity)")
- }
- }
- } else {
- dest.write_str("none")
- }
- }
-}
-
-/// A color with red, green, blue, and alpha components, in a byte each.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct RgbaLegacy {
- /// The red component.
- pub red: u8,
- /// The green component.
- pub green: u8,
- /// The blue component.
- pub blue: u8,
- /// The alpha component.
- pub alpha: f32,
-}
-
-impl RgbaLegacy {
- /// Constructs a new RGBA value from float components. It expects the red,
- /// green, blue and alpha channels in that order, and all values will be
- /// clamped to the 0.0 ... 1.0 range.
- #[inline]
- pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
- Self::new(
- clamp_unit_f32(red),
- clamp_unit_f32(green),
- clamp_unit_f32(blue),
- alpha.clamp(0.0, OPAQUE),
- )
- }
-
- /// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
- #[inline]
- pub const fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
- Self {
- red,
- green,
- blue,
- alpha,
- }
- }
-}
-
-#[cfg(feature = "serde")]
-impl Serialize for RgbaLegacy {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.red, self.green, self.blue, self.alpha).serialize(serializer)
- }
-}
-
-#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for RgbaLegacy {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (r, g, b, a) = Deserialize::deserialize(deserializer)?;
- Ok(RgbaLegacy::new(r, g, b, a))
- }
-}
-
-impl ToCss for RgbaLegacy {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- let has_alpha = self.alpha != OPAQUE;
-
- dest.write_str(if has_alpha { "rgba(" } else { "rgb(" })?;
- self.red.to_css(dest)?;
- dest.write_str(", ")?;
- self.green.to_css(dest)?;
- dest.write_str(", ")?;
- self.blue.to_css(dest)?;
-
- // Legacy syntax does not allow none components.
- serialize_color_alpha(dest, Some(self.alpha), true)?;
-
- dest.write_char(')')
- }
-}
-
-/// Color specified by hue, saturation and lightness components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Hsl {
- /// The hue component.
- pub hue: Option<f32>,
- /// The saturation component.
- pub saturation: Option<f32>,
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-impl Hsl {
- /// Construct a new HSL color from it's components.
- pub fn new(
- hue: Option<f32>,
- saturation: Option<f32>,
- lightness: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- hue,
- saturation,
- lightness,
- alpha,
- }
- }
-}
-
-impl ToCss for Hsl {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- // HSL serializes to RGB, so we have to convert it.
- let ColorComponents(red, green, blue) = hsl_to_rgb(&ColorComponents(
- self.hue.unwrap_or(0.0) / 360.0,
- self.saturation.unwrap_or(0.0),
- self.lightness.unwrap_or(0.0),
- ));
-
- RgbaLegacy::from_floats(red, green, blue, self.alpha.unwrap_or(OPAQUE)).to_css(dest)
- }
-}
-
-#[cfg(feature = "serde")]
-impl Serialize for Hsl {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.hue, self.saturation, self.lightness, self.alpha).serialize(serializer)
- }
-}
-
-#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for Hsl {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (lightness, a, b, alpha) = Deserialize::deserialize(deserializer)?;
- Ok(Self::new(lightness, a, b, alpha))
- }
-}
-
-/// Color specified by hue, whiteness and blackness components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Hwb {
- /// The hue component.
- pub hue: Option<f32>,
- /// The whiteness component.
- pub whiteness: Option<f32>,
- /// The blackness component.
- pub blackness: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-impl Hwb {
- /// Construct a new HWB color from it's components.
- pub fn new(
- hue: Option<f32>,
- whiteness: Option<f32>,
- blackness: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- hue,
- whiteness,
- blackness,
- alpha,
- }
- }
-}
-
-impl ToCss for Hwb {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- // HWB serializes to RGB, so we have to convert it.
- let ColorComponents(red, green, blue) = hwb_to_rgb(&ColorComponents(
- self.hue.unwrap_or(0.0) / 360.0,
- self.whiteness.unwrap_or(0.0),
- self.blackness.unwrap_or(0.0),
- ));
-
- RgbaLegacy::from_floats(red, green, blue, self.alpha.unwrap_or(OPAQUE)).to_css(dest)
- }
-}
-
-#[cfg(feature = "serde")]
-impl Serialize for Hwb {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.hue, self.whiteness, self.blackness, self.alpha).serialize(serializer)
- }
-}
-
-#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for Hwb {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (lightness, whiteness, blackness, alpha) = Deserialize::deserialize(deserializer)?;
- Ok(Self::new(lightness, whiteness, blackness, alpha))
- }
-}
-
-// NOTE: LAB and OKLAB is not declared inside the [impl_lab_like] macro,
-// because it causes cbindgen to ignore them.
-
-/// Color specified by lightness, a- and b-axis components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Lab {
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The a-axis component.
- pub a: Option<f32>,
- /// The b-axis component.
- pub b: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-/// Color specified by lightness, a- and b-axis components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Oklab {
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The a-axis component.
- pub a: Option<f32>,
- /// The b-axis component.
- pub b: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-macro_rules! impl_lab_like {
- ($cls:ident, $fname:literal) => {
- impl $cls {
- /// Construct a new Lab color format with lightness, a, b and alpha components.
- pub fn new(
- lightness: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- lightness,
- a,
- b,
- alpha,
- }
- }
- }
-
- #[cfg(feature = "serde")]
- impl Serialize for $cls {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.lightness, self.a, self.b, self.alpha).serialize(serializer)
- }
- }
-
- #[cfg(feature = "serde")]
- impl<'de> Deserialize<'de> for $cls {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (lightness, a, b, alpha) = Deserialize::deserialize(deserializer)?;
- Ok(Self::new(lightness, a, b, alpha))
- }
- }
-
- impl ToCss for $cls {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- dest.write_str($fname)?;
- dest.write_str("(")?;
- ModernComponent(&self.lightness).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.a).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.b).to_css(dest)?;
- serialize_color_alpha(dest, self.alpha, false)?;
- dest.write_char(')')
- }
- }
- };
-}
-
-impl_lab_like!(Lab, "lab");
-impl_lab_like!(Oklab, "oklab");
-
-// NOTE: LCH and OKLCH is not declared inside the [impl_lch_like] macro,
-// because it causes cbindgen to ignore them.
-
-/// Color specified by lightness, chroma and hue components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Lch {
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The chroma component.
- pub chroma: Option<f32>,
- /// The hue component.
- pub hue: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-/// Color specified by lightness, chroma and hue components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Oklch {
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The chroma component.
- pub chroma: Option<f32>,
- /// The hue component.
- pub hue: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-macro_rules! impl_lch_like {
- ($cls:ident, $fname:literal) => {
- impl $cls {
- /// Construct a new color with lightness, chroma and hue components.
- pub fn new(
- lightness: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- lightness,
- chroma,
- hue,
- alpha,
- }
- }
- }
-
- #[cfg(feature = "serde")]
- impl Serialize for $cls {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.lightness, self.chroma, self.hue, self.alpha).serialize(serializer)
- }
- }
-
- #[cfg(feature = "serde")]
- impl<'de> Deserialize<'de> for $cls {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (lightness, chroma, hue, alpha) = Deserialize::deserialize(deserializer)?;
- Ok(Self::new(lightness, chroma, hue, alpha))
- }
- }
-
- impl ToCss for $cls {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- dest.write_str($fname)?;
- dest.write_str("(")?;
- ModernComponent(&self.lightness).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.chroma).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.hue).to_css(dest)?;
- serialize_color_alpha(dest, self.alpha, false)?;
- dest.write_char(')')
- }
- }
- };
-}
-
-impl_lch_like!(Lch, "lch");
-impl_lch_like!(Oklch, "oklch");
-
-/// A color specified by the color() function.
-/// <https://drafts.csswg.org/css-color-4/#color-function>
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct ColorFunction {
- /// The color space for this color.
- pub color_space: PredefinedColorSpace,
- /// The first component of the color. Either red or x.
- pub c1: Option<f32>,
- /// The second component of the color. Either green or y.
- pub c2: Option<f32>,
- /// The third component of the color. Either blue or z.
- pub c3: Option<f32>,
- /// The alpha component of the color.
- pub alpha: Option<f32>,
-}
-
-impl ColorFunction {
- /// Construct a new color function definition with the given color space and
- /// color components.
- pub fn new(
- color_space: PredefinedColorSpace,
- c1: Option<f32>,
- c2: Option<f32>,
- c3: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- color_space,
- c1,
- c2,
- c3,
- alpha,
- }
- }
-}
-
-impl ToCss for ColorFunction {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- dest.write_str("color(")?;
- self.color_space.to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.c1).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.c2).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.c3).to_css(dest)?;
-
- serialize_color_alpha(dest, self.alpha, false)?;
-
- dest.write_char(')')
- }
-}
-
-/// Describes one of the value <color> values according to the CSS
-/// specification.
-///
-/// Most components are `Option<_>`, so when the value is `None`, that component
-/// serializes to the "none" keyword.
-///
-/// <https://drafts.csswg.org/css-color-4/#color-type>
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub enum Color {
- /// The 'currentcolor' keyword.
- CurrentColor,
- /// Specify sRGB colors directly by their red/green/blue/alpha chanels.
- Rgba(RgbaLegacy),
- /// Specifies a color in sRGB using hue, saturation and lightness components.
- Hsl(Hsl),
- /// Specifies a color in sRGB using hue, whiteness and blackness components.
- Hwb(Hwb),
- /// Specifies a CIELAB color by CIE Lightness and its a- and b-axis hue
- /// coordinates (red/green-ness, and yellow/blue-ness) using the CIE LAB
- /// rectangular coordinate model.
- Lab(Lab),
- /// Specifies a CIELAB color by CIE Lightness, Chroma, and hue using the
- /// CIE LCH cylindrical coordinate model.
- Lch(Lch),
- /// Specifies an Oklab color by Oklab Lightness and its a- and b-axis hue
- /// coordinates (red/green-ness, and yellow/blue-ness) using the Oklab
- /// rectangular coordinate model.
- Oklab(Oklab),
- /// Specifies an Oklab color by Oklab Lightness, Chroma, and hue using
- /// the OKLCH cylindrical coordinate model.
- Oklch(Oklch),
- /// Specifies a color in a predefined color space.
- ColorFunction(ColorFunction),
-}
-
-impl ToCss for Color {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- match *self {
- Color::CurrentColor => dest.write_str("currentcolor"),
- Color::Rgba(rgba) => rgba.to_css(dest),
- Color::Hsl(hsl) => hsl.to_css(dest),
- Color::Hwb(hwb) => hwb.to_css(dest),
- Color::Lab(lab) => lab.to_css(dest),
- Color::Lch(lch) => lch.to_css(dest),
- Color::Oklab(lab) => lab.to_css(dest),
- Color::Oklch(lch) => lch.to_css(dest),
- Color::ColorFunction(color_function) => color_function.to_css(dest),
- }
- }
-}
-
/// Either a number or a percentage.
pub enum NumberOrPercentage {
/// `<number>`.
@@ -989,7 +433,7 @@ impl NumberOrPercentage {
}
/// Either an angle or a number.
-pub enum AngleOrNumber {
+pub enum NumberOrAngle {
/// `<number>`.
Number {
/// The numeric value parsed, as a float.
@@ -1002,13 +446,13 @@ pub enum AngleOrNumber {
},
}
-impl AngleOrNumber {
- /// Return the angle in degrees. `AngleOrNumber::Number` is returned as
+impl NumberOrAngle {
+ /// Return the angle in degrees. `NumberOrAngle::Number` is returned as
/// degrees, because it is the canonical unit.
pub fn degrees(&self) -> f32 {
match *self {
- AngleOrNumber::Number { value } => value,
- AngleOrNumber::Angle { degrees } => degrees,
+ Self::Number { value } => value,
+ Self::Angle { degrees } => degrees,
}
}
}
@@ -1021,37 +465,14 @@ pub trait ColorParser<'i> {
/// The type that the parser will construct on a successful parse.
type Output: FromParsedColor;
- /// A custom error type that can be returned from the parsing functions.
- type Error: 'i;
-
/// Parse an `<angle>` or `<number>`.
///
/// Returns the result in degrees.
- fn parse_angle_or_number<'t>(
+ fn parse_number_or_angle<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
- let location = input.current_source_location();
- Ok(match *input.next()? {
- Token::Number { value, .. } => AngleOrNumber::Number { value },
- Token::Dimension {
- value: v, ref unit, ..
- } => {
- let degrees = match_ignore_ascii_case! { unit,
- "deg" => v,
- "grad" => v * 360. / 400.,
- "rad" => v * 360. / (2. * PI),
- "turn" => v * 360.,
- _ => {
- return Err(location.new_unexpected_token_error(Token::Ident(unit.clone())))
- }
- };
-
- AngleOrNumber::Angle { degrees }
- },
- ref t => return Err(location.new_unexpected_token_error(t.clone())),
- })
- }
+ allow_none: bool,
+ ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>>;
/// Parse a `<percentage>` value.
///
@@ -1059,47 +480,22 @@ pub trait ColorParser<'i> {
fn parse_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<f32, ParseError<'i, Self::Error>> {
- input.expect_percentage().map_err(From::from)
- }
+ 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, Self::Error>> {
- input.expect_number().map_err(From::from)
- }
+ 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, Self::Error>> {
- let location = input.current_source_location();
- Ok(match *input.next()? {
- Token::Number { value, .. } => NumberOrPercentage::Number { value },
- Token::Percentage { unit_value, .. } => NumberOrPercentage::Percentage { unit_value },
- ref t => return Err(location.new_unexpected_token_error(t.clone())),
- })
- }
-}
-
-/// Default implementation of a [`ColorParser`]
-pub struct DefaultColorParser;
-
-impl<'i> ColorParser<'i> for DefaultColorParser {
- type Output = Color;
- type Error = ();
-}
-
-impl Color {
- /// Parse a <color> value, per CSS Color Module Level 3.
- ///
- /// FIXME(#2) Deprecated CSS2 System Colors are not supported yet.
- pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i, ()>> {
- parse_color_with(&DefaultColorParser, input)
- }
+ allow_none: bool,
+ ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>;
}
/// This trait is used by the [`ColorParser`] to construct colors of any type.
@@ -1108,139 +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: Option<f32>,
- saturation: Option<f32>,
- lightness: Option<f32>,
- alpha: Option<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: Option<f32>,
- whiteness: Option<f32>,
- blackness: Option<f32>,
- alpha: Option<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: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>)
- -> Self;
+ 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: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<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: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<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: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<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: Option<f32>,
- c2: Option<f32>,
- c3: Option<f32>,
- alpha: Option<f32>,
+ c1: ColorComponent<NumberOrPercentage>,
+ c2: ColorComponent<NumberOrPercentage>,
+ c3: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self;
}
-
-impl FromParsedColor for Color {
- #[inline]
- fn from_current_color() -> Self {
- Color::CurrentColor
- }
-
- #[inline]
- fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
- Color::Rgba(RgbaLegacy::new(red, green, blue, alpha))
- }
-
- fn from_hsl(
- hue: Option<f32>,
- saturation: Option<f32>,
- lightness: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Hsl(Hsl::new(hue, saturation, lightness, alpha))
- }
-
- fn from_hwb(
- hue: Option<f32>,
- blackness: Option<f32>,
- whiteness: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Hwb(Hwb::new(hue, blackness, whiteness, alpha))
- }
-
- #[inline]
- fn from_lab(
- lightness: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Lab(Lab::new(lightness, a, b, alpha))
- }
-
- #[inline]
- fn from_lch(
- lightness: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Lch(Lch::new(lightness, chroma, hue, alpha))
- }
-
- #[inline]
- fn from_oklab(
- lightness: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Oklab(Oklab::new(lightness, a, b, alpha))
- }
-
- #[inline]
- fn from_oklch(
- lightness: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Oklch(Oklch::new(lightness, chroma, hue, alpha))
- }
-
- #[inline]
- fn from_color_function(
- color_space: PredefinedColorSpace,
- c1: Option<f32>,
- c2: Option<f32>,
- c3: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::ColorFunction(ColorFunction::new(color_space, c1, c2, c3, alpha))
- }
-}