#![deny(missing_docs)]
#[cfg(test)]
mod tests;
use cssparser::color::{
clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, serialize_color_alpha,
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 std::str::FromStr;
#[inline]
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),
"currentcolor" => Output::from_current_color(),
_ => {
let (r, g, b) = cssparser::color::parse_named_color(ident)?;
Output::from_rgba(r, g, b, OPAQUE)
}
})
}
pub fn parse_color_with<'i, 't, P>(
color_parser: &P,
input: &mut Parser<'i, 't>,
) -> Result<P::Output, ParseError<'i, P::Error>>
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::Ident(ref value) => parse_color_keyword(value),
Token::Function(ref name) => {
let name = name.clone();
return input.parse_nested_block(|arguments| {
parse_color_function(color_parser, name, arguments)
});
}
_ => Err(()),
}
.map_err(|()| location.new_unexpected_token_error(token.clone()))
}
#[inline]
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>>
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),
"lab" => parse_lab_like(color_parser, arguments, 100.0, 125.0, P::Output::from_lab),
"lch" => parse_lch_like(color_parser, arguments, 100.0, 150.0, P::Output::from_lch),
"oklab" => parse_lab_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklab),
"oklch" => parse_lch_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklch),
"color" => parse_color_with_color_space(color_parser, arguments),
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
}?;
arguments.expect_exhausted()?;
Ok(color)
}
#[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>,
{
Ok(color_parser
.parse_number_or_percentage(arguments)?
.unit_value()
.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>>
where
P: ColorParser<'i>,
{
Ok(if !arguments.is_exhausted() {
arguments.expect_comma()?;
parse_alpha_component(color_parser, arguments)?
} else {
OPAQUE
})
}
fn parse_modern_alpha<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
) -> Result<Option<f32>, ParseError<'i, P::Error>>
where
P: ColorParser<'i>,
{
if !arguments.is_exhausted() {
arguments.expect_delim('/')?;
parse_none_or(arguments, |p| parse_alpha_component(color_parser, p))
} else {
Ok(Some(OPAQUE))
}
}
#[inline]
fn parse_rgb<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
) -> Result<P::Output, ParseError<'i, P::Error>>
where
P: ColorParser<'i>,
{
let maybe_red = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?;
let is_legacy_syntax = maybe_red.is_some() && 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 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 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);
(red, green, blue, alpha)
};
Ok(P::Output::from_rgba(red, green, blue, alpha))
}
#[inline]
fn parse_hsl<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
) -> Result<P::Output, ParseError<'i, P::Error>>
where
P: ColorParser<'i>,
{
let maybe_hue = parse_none_or(arguments, |p| color_parser.parse_angle_or_number(p))?;
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 alpha = if is_legacy_syntax {
saturation = Some(color_parser.parse_percentage(arguments)?);
arguments.expect_comma()?;
lightness = Some(color_parser.parse_percentage(arguments)?);
Some(parse_legacy_alpha(color_parser, arguments)?)
} else {
saturation = parse_none_or(arguments, |p| color_parser.parse_percentage(p))?;
lightness = parse_none_or(arguments, |p| color_parser.parse_percentage(p))?;
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, 1.0));
let lightness = lightness.map(|l| l.clamp(0.0, 1.0));
Ok(P::Output::from_hsl(hue, saturation, lightness, alpha))
}
#[inline]
fn parse_hwb<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
) -> Result<P::Output, ParseError<'i, P::Error>>
where
P: ColorParser<'i>,
{
let (hue, whiteness, blackness, alpha) = parse_components(
color_parser,
arguments,
P::parse_angle_or_number,
P::parse_percentage,
P::parse_percentage,
)?;
let hue = hue.map(|h| normalize_hue(h.degrees()));
let whiteness = whiteness.map(|w| w.clamp(0.0, 1.0));
let blackness = blackness.map(|b| b.clamp(0.0, 1.0));
Ok(P::Output::from_hwb(hue, whiteness, blackness, alpha))
}
#[inline]
pub fn hwb_to_rgb(h: f32, w: f32, b: f32) -> (f32, f32, f32) {
if w + b >= 1.0 {
let gray = w / (w + b);
return (gray, gray, gray);
}
let (mut red, mut green, mut blue) = hsl_to_rgb(h, 1.0, 0.5);
let x = 1.0 - w - b;
red = red * x + w;
green = green * x + w;
blue = blue * x + w;
(red, green, blue)
}
#[inline]
pub fn hsl_to_rgb(hue: f32, saturation: f32, lightness: f32) -> (f32, f32, f32) {
debug_assert!((0.0..=1.0).contains(&hue));
fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
if h3 < 0. {
h3 += 3.
}
if h3 > 3. {
h3 -= 3.
}
if h3 * 2. < 1. {
m1 + (m2 - m1) * h3 * 2.
} else if h3 * 2. < 3. {
m2
} else if h3 < 2. {
m1 + (m2 - m1) * (2. - h3) * 2.
} else {
m1
}
}
let m2 = if lightness <= 0.5 {
lightness * (saturation + 1.)
} else {
lightness + saturation - lightness * saturation
};
let m1 = lightness * 2. - m2;
let hue_times_3 = hue * 3.;
let red = hue_to_rgb(m1, m2, hue_times_3 + 1.);
let green = hue_to_rgb(m1, m2, hue_times_3);
let blue = hue_to_rgb(m1, m2, hue_times_3 - 1.);
(red, green, blue)
}
type IntoColorFn<Output> =
fn(l: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>) -> 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>>
where
P: ColorParser<'i>,
{
let (lightness, a, b, alpha) = parse_components(
color_parser,
arguments,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
)?;
let lightness = lightness.map(|l| l.value(lightness_range));
let a = a.map(|a| a.value(a_b_range));
let b = b.map(|b| b.value(a_b_range));
Ok(into_color(lightness, a, b, alpha))
}
#[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>>
where
P: ColorParser<'i>,
{
let (lightness, chroma, hue, alpha) = parse_components(
color_parser,
arguments,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
P::parse_angle_or_number,
)?;
let lightness = lightness.map(|l| l.value(lightness_range));
let chroma = chroma.map(|c| c.value(chroma_range));
let hue = hue.map(|h| normalize_hue(h.degrees()));
Ok(into_color(lightness, chroma, hue, alpha))
}
#[inline]
fn parse_color_with_color_space<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
) -> Result<P::Output, ParseError<'i, P::Error>>
where
P: ColorParser<'i>,
{
let color_space = {
let location = arguments.current_source_location();
let ident = arguments.expect_ident()?;
PredefinedColorSpace::from_str(ident)
.map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))?
};
let (c1, c2, c3, alpha) = parse_components(
color_parser,
arguments,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
)?;
let c1 = c1.map(|c| c.unit_value());
let c2 = c2.map(|c| c.unit_value());
let c3 = c3.map(|c| c.unit_value());
Ok(P::Output::from_color_function(
color_space,
c1,
c2,
c3,
alpha,
))
}
type ComponentParseResult<'i, R1, R2, R3, Error> =
Result<(Option<R1>, Option<R2>, Option<R3>, Option<f32>), ParseError<'i, Error>>;
pub fn parse_components<'i, 't, P, F1, F2, F3, R1, R2, R3>(
color_parser: &P,
input: &mut Parser<'i, 't>,
f1: F1,
f2: F2,
f3: F3,
) -> ComponentParseResult<'i, R1, R2, R3, P::Error>
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>>,
{
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 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)?)),
}
}
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")
}
}
}
fn normalize_hue(hue: f32) -> f32 {
hue - 360.0 * (hue / 360.0).floor()
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct RgbaLegacy {
pub red: u8,
pub green: u8,
pub blue: u8,
pub alpha: f32,
}
impl RgbaLegacy {
#[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),
)
}
#[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)?;
serialize_color_alpha(dest, Some(self.alpha), true)?;
dest.write_char(')')
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Hsl {
pub hue: Option<f32>,
pub saturation: Option<f32>,
pub lightness: Option<f32>,
pub alpha: Option<f32>,
}
impl Hsl {
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,
{
let (red, green, blue) = hsl_to_rgb(
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))
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Hwb {
pub hue: Option<f32>,
pub whiteness: Option<f32>,
pub blackness: Option<f32>,
pub alpha: Option<f32>,
}
impl Hwb {
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,
{
let (red, green, blue) = hwb_to_rgb(
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))
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Lab {
pub lightness: Option<f32>,
pub a: Option<f32>,
pub b: Option<f32>,
pub alpha: Option<f32>,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Oklab {
pub lightness: Option<f32>,
pub a: Option<f32>,
pub b: Option<f32>,
pub alpha: Option<f32>,
}
macro_rules! impl_lab_like {
($cls:ident, $fname:literal) => {
impl $cls {
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");
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Lch {
pub lightness: Option<f32>,
pub chroma: Option<f32>,
pub hue: Option<f32>,
pub alpha: Option<f32>,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Oklch {
pub lightness: Option<f32>,
pub chroma: Option<f32>,
pub hue: Option<f32>,
pub alpha: Option<f32>,
}
macro_rules! impl_lch_like {
($cls:ident, $fname:literal) => {
impl $cls {
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");
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct ColorFunction {
pub color_space: PredefinedColorSpace,
pub c1: Option<f32>,
pub c2: Option<f32>,
pub c3: Option<f32>,
pub alpha: Option<f32>,
}
impl ColorFunction {
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(')')
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Color {
CurrentColor,
Rgba(RgbaLegacy),
Hsl(Hsl),
Hwb(Hwb),
Lab(Lab),
Lch(Lch),
Oklab(Oklab),
Oklch(Oklch),
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),
}
}
}
pub enum NumberOrPercentage {
Number {
value: f32,
},
Percentage {
unit_value: f32,
},
}
impl NumberOrPercentage {
pub fn unit_value(&self) -> f32 {
match *self {
NumberOrPercentage::Number { value } => value,
NumberOrPercentage::Percentage { unit_value } => unit_value,
}
}
pub fn value(&self, percentage_basis: f32) -> f32 {
match *self {
Self::Number { value } => value,
Self::Percentage { unit_value } => unit_value * percentage_basis,
}
}
}
pub enum AngleOrNumber {
Number {
value: f32,
},
Angle {
degrees: f32,
},
}
impl AngleOrNumber {
pub fn degrees(&self) -> f32 {
match *self {
AngleOrNumber::Number { value } => value,
AngleOrNumber::Angle { degrees } => degrees,
}
}
}
pub trait ColorParser<'i> {
type Output: FromParsedColor;
type Error: 'i;
fn parse_angle_or_number<'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())),
})
}
fn parse_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<f32, ParseError<'i, Self::Error>> {
input.expect_percentage().map_err(From::from)
}
fn parse_number<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<f32, ParseError<'i, Self::Error>> {
input.expect_number().map_err(From::from)
}
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())),
})
}
}
pub struct DefaultColorParser;
impl<'i> ColorParser<'i> for DefaultColorParser {
type Output = Color;
type Error = ();
}
impl Color {
pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i, ()>> {
parse_color_with(&DefaultColorParser, input)
}
}
pub trait FromParsedColor {
fn from_current_color() -> Self;
fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self;
fn from_hsl(
hue: Option<f32>,
saturation: Option<f32>,
lightness: Option<f32>,
alpha: Option<f32>,
) -> Self;
fn from_hwb(
hue: Option<f32>,
whiteness: Option<f32>,
blackness: Option<f32>,
alpha: Option<f32>,
) -> Self;
fn from_lab(lightness: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>)
-> Self;
fn from_lch(
lightness: Option<f32>,
chroma: Option<f32>,
hue: Option<f32>,
alpha: Option<f32>,
) -> Self;
fn from_oklab(
lightness: Option<f32>,
a: Option<f32>,
b: Option<f32>,
alpha: Option<f32>,
) -> Self;
fn from_oklch(
lightness: Option<f32>,
chroma: Option<f32>,
hue: Option<f32>,
alpha: Option<f32>,
) -> Self;
fn from_color_function(
color_space: PredefinedColorSpace,
c1: Option<f32>,
c2: Option<f32>,
c3: Option<f32>,
alpha: Option<f32>,
) -> 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))
}
}