From 134125dda88e29032d903d9d8deeb33db741fb20 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Fri, 14 Apr 2023 12:37:21 -0400 Subject: [PATCH 1/3] Update relative color parsing to latest spec --- src/lib.rs | 57 ++++--- src/properties/custom.rs | 17 +- src/values/angle.rs | 17 +- src/values/calc.rs | 35 ++-- src/values/color.rs | 297 ++++++++++++++++++++------------- src/values/length.rs | 2 +- src/values/percentage.rs | 24 ++- src/values/time.rs | 10 +- website/pages/transpilation.md | 4 +- 9 files changed, 282 insertions(+), 181 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 997a5aa5..8d021d34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14312,7 +14312,7 @@ mod tests { } test("lab(from indianred calc(l * .8) a b)", "lab(43.1402% 45.7516 23.1557)"); - test("lch(from indianred calc(l + 10%) c h)", "lch(63.9252% 51.2776 26.8448)"); + test("lch(from indianred calc(l + 10) c h)", "lch(63.9252% 51.2776 26.8448)"); test("lch(from indianred l calc(c - 50) h)", "lch(53.9252% 1.27763 26.8448)"); test( "lch(from indianred l c calc(h + 180deg))", @@ -14328,12 +14328,23 @@ mod tests { "rgba(205, 92, 92, .7)", ); test( - "rgb(from rgba(205, 92, 92, .5) r g b / calc(alpha + 20%))", + "rgb(from rgba(205, 92, 92, .5) r g b / calc(alpha + .2))", "rgba(205, 92, 92, .7)", ); test("lch(from indianred l sin(c) h)", "lch(53.9252% .84797 26.8448)"); test("lch(from indianred l sqrt(c) h)", "lch(53.9252% 7.16084 26.8448)"); - test("lch(from indianred l c sin(h))", "lch(53.9252% 51.2776 .990043)"); + test("lch(from indianred l c sin(h))", "lch(53.9252% 51.2776 .451575)"); + test("lch(from indianred calc(10% + 20%) c h)", "lch(30% 51.2776 26.8448)"); + test("lch(from indianred calc(10 + 20) c h)", "lch(30% 51.2776 26.8448)"); + test("lch(from indianred l c calc(10 + 20))", "lch(53.9252% 51.2776 30)"); + test( + "lch(from indianred l c calc(10deg + 20deg))", + "lch(53.9252% 51.2776 30)", + ); + test( + "lch(from indianred l c calc(10deg + 0.35rad))", + "lch(53.9252% 51.2776 30.0535)", + ); // The following tests were converted from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-color/parsing/relative-color-valid.html // Find: test_valid_value\(`color`, `(.*?)`,\s*`(.*?)`\) @@ -14443,21 +14454,15 @@ mod tests { // Testing permutation. test("rgb(from rebeccapurple g b r)", "rgb(51, 153, 102)"); - test("rgb(from rebeccapurple b alpha r / g)", "rgba(153, 255, 102, 0.2)"); - test("rgb(from rebeccapurple r r r / r)", "rgba(102, 102, 102, 0.4)"); - test( - "rgb(from rebeccapurple alpha alpha alpha / alpha)", - "rgb(255, 255, 255)", - ); + test("rgb(from rebeccapurple b alpha r / g)", "rgba(153, 1, 102, 1)"); + test("rgb(from rebeccapurple r r r / r)", "rgba(102, 102, 102, 1)"); + test("rgb(from rebeccapurple alpha alpha alpha / alpha)", "rgb(1, 1, 1)"); test("rgb(from rgb(20%, 40%, 60%, 80%) g b r)", "rgb(102, 153, 51)"); - test( - "rgb(from rgb(20%, 40%, 60%, 80%) b alpha r / g)", - "rgba(153, 204, 51, 0.4)", - ); - test("rgb(from rgb(20%, 40%, 60%, 80%) r r r / r)", "rgba(51, 51, 51, 0.2)"); + test("rgb(from rgb(20%, 40%, 60%, 80%) b alpha r / g)", "rgba(153, 1, 51, 1)"); + test("rgb(from rgb(20%, 40%, 60%, 80%) r r r / r)", "rgba(51, 51, 51, 1)"); test( "rgb(from rgb(20%, 40%, 60%, 80%) alpha alpha alpha / alpha)", - "rgba(204, 204, 204, 0.8)", + "rgba(1, 1, 1, 0.8)", ); // Testing mixes of number and percentage. (These would not be allowed in the non-relative syntax). @@ -15182,7 +15187,11 @@ mod tests { // NOTE: 'c' is a vaild hue, as hue is |. test( &format!("{}(from {}(70% 45 30) alpha c h / l)", color_space, color_space), - &format!("{}(100% 45 30 / 0.7)", color_space), + &format!( + "{}(1 45 30 / {})", + color_space, + if *color_space == "lch" { "1" } else { ".7" } + ), ); test( &format!("{}(from {}(70% 45 30) l c c / alpha)", color_space, color_space), @@ -15190,15 +15199,19 @@ mod tests { ); test( &format!("{}(from {}(70% 45 30) alpha c h / alpha)", color_space, color_space), - &format!("{}(100% 45 30)", color_space), + &format!("{}(1 45 30)", color_space), ); test( &format!("{}(from {}(70% 45 30) alpha c c / alpha)", color_space, color_space), - &format!("{}(100% 45 45)", color_space), + &format!("{}(1 45 45)", color_space), ); test( &format!("{}(from {}(70% 45 30 / 40%) alpha c h / l)", color_space, color_space), - &format!("{}(40% 45 30 / 0.7)", color_space), + &format!( + "{}(.4 45 30 / {})", + color_space, + if *color_space == "lch" { "1" } else { ".7" } + ), ); test( &format!("{}(from {}(70% 45 30 / 40%) l c c / alpha)", color_space, color_space), @@ -15209,14 +15222,14 @@ mod tests { "{}(from {}(70% 45 30 / 40%) alpha c h / alpha)", color_space, color_space ), - &format!("{}(40% 45 30 / 0.4)", color_space), + &format!("{}(.4 45 30 / 0.4)", color_space), ); test( &format!( "{}(from {}(70% 45 30 / 40%) alpha c c / alpha)", color_space, color_space ), - &format!("{}(40% 45 45 / 0.4)", color_space), + &format!("{}(.4 45 45 / 0.4)", color_space), ); // Testing with calc(). @@ -16084,7 +16097,7 @@ mod tests { ); minify_test( ".foo{color:hsl(from rebeccapurple alpha alpha alpha / alpha)}", - ".foo{color:hsl(from rebeccapurple alpha alpha alpha/alpha)}", + ".foo{color:#fff}", ); } } diff --git a/src/properties/custom.rs b/src/properties/custom.rs index 4fff2bc0..81770d37 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -1479,21 +1479,16 @@ impl<'i> UnresolvedColor<'i> { where W: std::fmt::Write, { - #[inline] - fn c(c: &f32) -> i32 { - (c * 255.0).round().clamp(0.0, 255.0) as i32 - } - match self { UnresolvedColor::RGB { r, g, b, alpha } => { if let Some(targets) = dest.targets { if !compat::Feature::SpaceSeparatedColorFunction.is_compatible(targets) { dest.write_str("rgba(")?; - c(r).to_css(dest)?; + r.to_css(dest)?; dest.delim(',', false)?; - c(g).to_css(dest)?; + g.to_css(dest)?; dest.delim(',', false)?; - c(b).to_css(dest)?; + b.to_css(dest)?; dest.delim(',', false)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(')')?; @@ -1502,11 +1497,11 @@ impl<'i> UnresolvedColor<'i> { } dest.write_str("rgb(")?; - c(r).to_css(dest)?; + r.to_css(dest)?; dest.write_char(' ')?; - c(g).to_css(dest)?; + g.to_css(dest)?; dest.write_char(' ')?; - c(b).to_css(dest)?; + b.to_css(dest)?; dest.delim('/', true)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(')') diff --git a/src/values/angle.rs b/src/values/angle.rs index 3f2ccfc0..ad63a39a 100644 --- a/src/values/angle.rs +++ b/src/values/angle.rs @@ -182,11 +182,13 @@ impl Into> for Angle { } } -impl From> for Angle { - fn from(calc: Calc) -> Angle { +impl TryFrom> for Angle { + type Error = (); + + fn try_from(calc: Calc) -> Result { match calc { - Calc::Value(v) => *v, - _ => unreachable!(), + Calc::Value(v) => Ok(*v), + _ => Err(()), } } } @@ -284,6 +286,13 @@ macro_rules! impl_try_from_angle { Err(()) } } + + impl TryInto for $t { + type Error = (); + fn try_into(self) -> Result { + Err(()) + } + } }; } diff --git a/src/values/calc.rs b/src/values/calc.rs index 77f1e5f7..9225bb2f 100644 --- a/src/values/calc.rs +++ b/src/values/calc.rs @@ -267,8 +267,9 @@ impl< + TrySign + std::cmp::PartialOrd + Into> - + From> + + TryFrom> + TryFrom + + TryInto + Clone + std::fmt::Debug, > Parse<'i> for Calc @@ -288,8 +289,9 @@ impl< + TrySign + std::cmp::PartialOrd + Into> - + From> + + TryFrom> + TryFrom + + TryInto + Clone + std::fmt::Debug, > Calc @@ -501,12 +503,12 @@ impl< match *input.next()? { Token::Delim('+') => { let next = Calc::parse_product(input, parse_ident)?; - cur = cur.add(next); + cur = cur.add(next).map_err(|_| input.new_custom_error(ParserError::InvalidValue))?; } Token::Delim('-') => { let mut rhs = Calc::parse_product(input, parse_ident)?; rhs = rhs * -1.0; - cur = cur.add(rhs); + cur = cur.add(rhs).map_err(|_| input.new_custom_error(ParserError::InvalidValue))?; } ref t => { let t = t.clone(); @@ -695,9 +697,12 @@ impl< ) -> Result>> { input.parse_nested_block(|input| { let v: Calc = Calc::parse_sum(input, |v| { - parse_ident(v).and_then(|v| match v { - Calc::Number(v) => Some(Calc::Number(v)), - _ => None, + parse_ident(v).and_then(|v| -> Option> { + match v { + Calc::Number(v) => Some(Calc::Number(v)), + Calc::Value(v) => (*v).try_into().ok().map(|v| Calc::Value(Box::new(v))), + _ => None, + } }) })?; let rad = match v { @@ -854,19 +859,17 @@ impl> std::ops::Mul for Calc { } } -impl> + std::convert::From> + std::fmt::Debug> AddInternal - for Calc -{ - fn add(self, other: Calc) -> Calc { - match (self, other) { +impl> + std::convert::TryFrom> + std::fmt::Debug> Calc { + pub(crate) fn add(self, other: Calc) -> Result, >>::Error> { + Ok(match (self, other) { (Calc::Value(a), Calc::Value(b)) => (a.add(*b)).into(), (Calc::Number(a), Calc::Number(b)) => Calc::Number(a + b), - (Calc::Value(a), b) => (a.add(V::from(b))).into(), - (a, Calc::Value(b)) => (V::from(a).add(*b)).into(), + (Calc::Value(a), b) => (a.add(V::try_from(b)?)).into(), + (a, Calc::Value(b)) => (V::try_from(a)?.add(*b)).into(), (Calc::Function(a), b) => Calc::Sum(Box::new(Calc::Function(a)), Box::new(b)), (a, Calc::Function(b)) => Calc::Sum(Box::new(a), Box::new(Calc::Function(b))), - (a, b) => V::from(a).add(V::from(b)).into(), - } + (a, b) => V::try_from(a)?.add(V::try_from(b)?).into(), + }) } } diff --git a/src/values/color.rs b/src/values/color.rs index 50fd7be6..ddeedbe1 100644 --- a/src/values/color.rs +++ b/src/values/color.rs @@ -92,7 +92,7 @@ impl CurrentColor { #[serde(tag = "type", rename_all = "lowercase")] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] enum RGBColor { - RGB(SRGB), + RGB(RGB), } #[cfg(feature = "serde")] @@ -180,7 +180,7 @@ pub enum PredefinedColor { #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum FloatColor { /// An RGB color. - RGB(SRGB), + RGB(RGB), /// An HSL color. HSL(HSL), /// An HWB color. @@ -568,21 +568,23 @@ impl RelativeComponentParser { } } - fn get_ident(&self, ident: &str, allowed_types: ChannelType) -> Option { + fn get_ident(&self, ident: &str, allowed_types: ChannelType) -> Option<(f32, ChannelType)> { if ident.eq_ignore_ascii_case(self.names.0) && allowed_types.intersects(self.types.0) { - return Some(self.components.0); + return Some((self.components.0, self.types.0)); } if ident.eq_ignore_ascii_case(self.names.1) && allowed_types.intersects(self.types.1) { - return Some(self.components.1); + return Some((self.components.1, self.types.1)); } if ident.eq_ignore_ascii_case(self.names.2) && allowed_types.intersects(self.types.2) { - return Some(self.components.2); + return Some((self.components.2, self.types.2)); } - if ident.eq_ignore_ascii_case("alpha") && allowed_types.intersects(ChannelType::Percentage) { - return Some(self.components.3); + if ident.eq_ignore_ascii_case("alpha") + && allowed_types.intersects(ChannelType::Number | ChannelType::Percentage) + { + return Some((self.components.3, ChannelType::Number)); } None @@ -592,24 +594,12 @@ impl RelativeComponentParser { &self, input: &mut Parser<'i, 't>, allowed_types: ChannelType, - ) -> Result>> { + ) -> Result<(f32, ChannelType), ParseError<'i, ParserError<'i>>> { match self.get_ident(input.expect_ident()?.as_ref(), allowed_types) { Some(v) => Ok(v), None => Err(input.new_error_for_next_token()), } } - - fn parse_calc<'i, 't>( - &self, - input: &mut Parser<'i, 't>, - allowed_types: ChannelType, - ) -> Result>> { - match Calc::parse_with(input, |ident| self.get_ident(ident, allowed_types).map(Calc::Number)) { - Ok(Calc::Value(v)) => Ok(*v), - Ok(Calc::Number(n)) => Ok(n), - _ => Err(input.new_custom_error(ParserError::InvalidValue)), - } - } } impl<'i> ColorComponentParser<'i> for RelativeComponentParser { @@ -619,46 +609,55 @@ impl<'i> ColorComponentParser<'i> for RelativeComponentParser { &self, input: &mut Parser<'i, 't>, ) -> Result> { - if let Ok(value) = input.try_parse(|input| self.parse_ident(input, ChannelType::Angle | ChannelType::Number)) { - return Ok(AngleOrNumber::Number { value }); - } - - if let Ok(value) = input.try_parse(|input| self.parse_calc(input, ChannelType::Angle | ChannelType::Number)) { - return Ok(AngleOrNumber::Number { value }); + if let Ok((value, ty)) = + input.try_parse(|input| self.parse_ident(input, ChannelType::Angle | ChannelType::Number)) + { + return Ok(match ty { + ChannelType::Angle => AngleOrNumber::Angle { degrees: value }, + ChannelType::Number => AngleOrNumber::Number { value }, + _ => unreachable!(), + }); } - if let Ok(value) = input.try_parse(|input| -> Result>> { + if let Ok(value) = input.try_parse(|input| -> Result>> { match Calc::parse_with(input, |ident| { self .get_ident(ident, ChannelType::Angle | ChannelType::Number) - .map(|v| Calc::Value(Box::new(Angle::Deg(v)))) + .map(|(value, ty)| match ty { + ChannelType::Angle => Calc::Value(Box::new(Angle::Deg(value))), + ChannelType::Number => Calc::Number(value), + _ => unreachable!(), + }) }) { - Ok(Calc::Value(v)) => Ok(*v), + Ok(Calc::Value(v)) => Ok(AngleOrNumber::Angle { + degrees: v.to_degrees(), + }), + Ok(Calc::Number(v)) => Ok(AngleOrNumber::Number { value: v }), _ => Err(input.new_custom_error(ParserError::InvalidValue)), } }) { - return Ok(AngleOrNumber::Angle { - degrees: value.to_degrees(), - }); + return Ok(value); } Err(input.new_error_for_next_token()) } fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result> { - if let Ok(value) = input.try_parse(|input| self.parse_ident(input, ChannelType::Number)) { + if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Number)) { return Ok(value); } - if let Ok(value) = input.try_parse(|input| self.parse_calc(input, ChannelType::Number)) { - return Ok(value); + match Calc::parse_with(input, |ident| { + self.get_ident(ident, ChannelType::Number).map(|(v, _)| Calc::Number(v)) + }) { + Ok(Calc::Value(v)) => Ok(*v), + Ok(Calc::Number(n)) => Ok(n), + _ => Err(input.new_error_for_next_token()), } - - Err(input.new_error_for_next_token()) } fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result> { - if let Ok(value) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage)) { + if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage)) { return Ok(value); } @@ -666,7 +665,7 @@ impl<'i> ColorComponentParser<'i> for RelativeComponentParser { match Calc::parse_with(input, |ident| { self .get_ident(ident, ChannelType::Percentage) - .map(|v| Calc::Value(Box::new(Percentage(v)))) + .map(|(v, _)| Calc::Value(Box::new(Percentage(v)))) }) { Ok(Calc::Value(v)) => Ok(*v), _ => Err(input.new_custom_error(ParserError::InvalidValue)), @@ -682,29 +681,32 @@ impl<'i> ColorComponentParser<'i> for RelativeComponentParser { &self, input: &mut Parser<'i, 't>, ) -> Result> { - if let Ok(value) = + if let Ok((value, ty)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage | ChannelType::Number)) { - return Ok(NumberOrPercentage::Percentage { unit_value: value }); - } - - if let Ok(value) = - input.try_parse(|input| self.parse_calc(input, ChannelType::Percentage | ChannelType::Number)) - { - return Ok(NumberOrPercentage::Percentage { unit_value: value }); + return Ok(match ty { + ChannelType::Percentage => NumberOrPercentage::Percentage { unit_value: value }, + ChannelType::Number => NumberOrPercentage::Number { value }, + _ => unreachable!(), + }); } - if let Ok(value) = input.try_parse(|input| -> Result>> { + if let Ok(value) = input.try_parse(|input| -> Result>> { match Calc::parse_with(input, |ident| { self .get_ident(ident, ChannelType::Percentage | ChannelType::Number) - .map(|v| Calc::Value(Box::new(Percentage(v)))) + .map(|(value, ty)| match ty { + ChannelType::Percentage => Calc::Value(Box::new(Percentage(value))), + ChannelType::Number => Calc::Number(value), + _ => unreachable!(), + }) }) { - Ok(Calc::Value(v)) => Ok(*v), + Ok(Calc::Value(v)) => Ok(NumberOrPercentage::Percentage { unit_value: v.0 }), + Ok(Calc::Number(v)) => Ok(NumberOrPercentage::Number { value: v }), _ => Err(input.new_custom_error(ParserError::InvalidValue)), } }) { - return Ok(NumberOrPercentage::Percentage { unit_value: value.0 }); + return Ok(value); } Err(input.new_error_for_next_token()) @@ -859,7 +861,7 @@ fn parse_color_function<'i, 't>(input: &mut Parser<'i, 't>) -> Result { let (r, g, b, a) = parse_rgb(input, &mut parser)?; - Ok(CssColor::Float(Box::new(FloatColor::RGB(SRGB { r, g, b, alpha: a })))) + Ok(CssColor::Float(Box::new(FloatColor::RGB(RGB { r, g, b, alpha: a })))) }, "color-mix" => { input.parse_nested_block(parse_color_mix) @@ -1028,7 +1030,7 @@ fn parse_rgb<'i, 't>( ) -> Result<(f32, f32, f32, f32), ParseError<'i, ParserError<'i>>> { // https://drafts.csswg.org/css-color-4/#rgb-functions let res = input.parse_nested_block(|input| { - parser.parse_relative::(input)?; + parser.parse_relative::(input)?; let (r, g, b) = parse_rgb_components(input, parser)?; let alpha = parse_alpha(input, parser)?; Ok((r, g, b, alpha)) @@ -1062,10 +1064,10 @@ pub(crate) fn parse_rgb_components<'i, 't>( Ok(match parser.parse_number_or_percentage(input)? { NumberOrPercentage::Number { value } if value.is_nan() => (value, kind), NumberOrPercentage::Number { value } if parser.from.is_some() || kind != Kind::Percentage => { - (value.round().clamp(0.0, 255.0) / 255.0, Kind::Number) + (value.round().clamp(0.0, 255.0), Kind::Number) } NumberOrPercentage::Percentage { unit_value } if parser.from.is_some() || kind != Kind::Number => { - (unit_value.clamp(0.0, 1.0), Kind::Percentage) + ((unit_value * 255.0).round().clamp(0.0, 255.0), Kind::Percentage) } _ => return Err(input.new_custom_error(ParserError::InvalidValue)), }) @@ -1293,50 +1295,36 @@ define_colorspace! { /// A color in the [`sRGB`](https://www.w3.org/TR/css-color-4/#predefined-sRGB) color space. pub struct SRGB { /// The red component. - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))] - r: Percentage, + r: Number, /// The green component. - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))] - g: Percentage, + g: Number, /// The blue component. - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))] - b: Percentage + b: Number } } -// serialize RGB components in the 0-255 range as it is more common. -#[cfg(feature = "serde")] -fn serialize_rgb_component(v: &f32, serializer: S) -> Result -where - S: serde::Serializer, -{ - let v = if !v.is_nan() { - (v * 255.0).round().max(0.0).min(255.0) - } else { - *v - }; - - serializer.serialize_f32(v) -} - -#[cfg(feature = "serde")] -fn deserialize_rgb_component<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let v: f32 = serde::Deserialize::deserialize(deserializer)?; - Ok(v / 255.0) +define_colorspace! { + /// A color in the [`RGB`](https://w3c.github.io/csswg-drafts/css-color-4/#rgb-functions) color space. + /// Components are in the 0-255 range. + pub struct RGB { + /// The red component. + r: Number, + /// The green component. + g: Number, + /// The blue component. + b: Number + } } define_colorspace! { /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space. pub struct SRGBLinear { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1344,11 +1332,11 @@ define_colorspace! { /// A color in the [`display-p3`](https://www.w3.org/TR/css-color-4/#predefined-display-p3) color space. pub struct P3 { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1356,11 +1344,11 @@ define_colorspace! { /// A color in the [`a98-rgb`](https://www.w3.org/TR/css-color-4/#predefined-a98-rgb) color space. pub struct A98 { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1368,11 +1356,11 @@ define_colorspace! { /// A color in the [`prophoto-rgb`](https://www.w3.org/TR/css-color-4/#predefined-prophoto-rgb) color space. pub struct ProPhoto { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1380,11 +1368,11 @@ define_colorspace! { /// A color in the [`rec2020`](https://www.w3.org/TR/css-color-4/#predefined-rec2020) color space. pub struct Rec2020 { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1392,7 +1380,7 @@ define_colorspace! { /// A color in the [CIE Lab](https://www.w3.org/TR/css-color-4/#cie-lab) color space. pub struct LAB { /// The lightness component. - l: Percentage, + l: Number, /// The a component. a: Number, /// The b component. @@ -1404,7 +1392,7 @@ define_colorspace! { /// A color in the [CIE LCH](https://www.w3.org/TR/css-color-4/#cie-lab) color space. pub struct LCH { /// The lightness component. - l: Percentage, + l: Number, /// The chroma component. c: Number, /// The hue component. @@ -1416,7 +1404,7 @@ define_colorspace! { /// A color in the [OKLab](https://www.w3.org/TR/css-color-4/#ok-lab) color space. pub struct OKLAB { /// The lightness component. - l: Percentage, + l: Number, /// The a component. a: Number, /// The b component. @@ -1428,7 +1416,7 @@ define_colorspace! { /// A color in the [OKLCH](https://www.w3.org/TR/css-color-4/#ok-lab) color space. pub struct OKLCH { /// The lightness component. - l: Percentage, + l: Number, /// The chroma component. c: Number, /// The hue component. @@ -1440,11 +1428,11 @@ define_colorspace! { /// A color in the [`xyz-d50`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space. pub struct XYZd50 { /// The x component. - x: Percentage, + x: Number, /// The y component. - y: Percentage, + y: Number, /// The z component. - z: Percentage + z: Number } } @@ -1452,11 +1440,11 @@ define_colorspace! { /// A color in the [`xyz-d65`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space. pub struct XYZd65 { /// The x component. - x: Percentage, + x: Number, /// The y component. - y: Percentage, + y: Number, /// The z component. - z: Percentage + z: Number } } @@ -2383,13 +2371,7 @@ impl From for SRGB { } impl From for SRGB { - fn from(rgb: RGBA) -> Self { - Self::from(&rgb) - } -} - -impl From<&RGBA> for SRGB { - fn from(rgb: &RGBA) -> SRGB { + fn from(rgb: RGBA) -> SRGB { SRGB { r: rgb.red_f32(), g: rgb.green_f32(), @@ -2406,6 +2388,56 @@ impl From for RGBA { } } +impl From for RGB { + fn from(rgb: SRGB) -> Self { + RGB { + r: rgb.r * 255.0, + g: rgb.g * 255.0, + b: rgb.b * 255.0, + alpha: rgb.alpha, + } + } +} + +impl From for SRGB { + fn from(rgb: RGB) -> Self { + SRGB { + r: rgb.r / 255.0, + g: rgb.g / 255.0, + b: rgb.b / 255.0, + alpha: rgb.alpha, + } + } +} + +impl From for RGB { + fn from(rgb: RGBA) -> Self { + RGB::from(&rgb) + } +} + +impl From<&RGBA> for RGB { + fn from(rgb: &RGBA) -> Self { + RGB { + r: rgb.red as f32, + g: rgb.green as f32, + b: rgb.blue as f32, + alpha: rgb.alpha_f32(), + } + } +} + +impl From for RGBA { + fn from(rgb: RGB) -> Self { + RGBA::new( + rgb.r as u8, + rgb.g as u8, + rgb.b as u8, + (rgb.alpha * 255.0).round().max(0.).min(255.) as u8, + ) + } +} + // Once Rust specialization is stable, this could be simplified. via!(LAB -> XYZd50 -> XYZd65); via!(ProPhoto -> XYZd50 -> XYZd65); @@ -2497,6 +2529,20 @@ via!(HWB -> SRGB -> XYZd65); via!(HWB -> XYZd65 -> OKLAB); via!(HWB -> XYZd65 -> OKLCH); +via!(RGB -> SRGB -> LAB); +via!(RGB -> SRGB -> LCH); +via!(RGB -> SRGB -> OKLAB); +via!(RGB -> SRGB -> OKLCH); +via!(RGB -> SRGB -> P3); +via!(RGB -> SRGB -> SRGBLinear); +via!(RGB -> SRGB -> A98); +via!(RGB -> SRGB -> ProPhoto); +via!(RGB -> SRGB -> XYZd50); +via!(RGB -> SRGB -> XYZd65); +via!(RGB -> SRGB -> Rec2020); +via!(RGB -> SRGB -> HSL); +via!(RGB -> SRGB -> HWB); + // RGBA is an 8-bit version. Convert to SRGB, which is a // more accurate floating point representation for all operations. via!(RGBA -> SRGB -> LAB); @@ -2597,6 +2643,7 @@ color_space!(ProPhoto); color_space!(Rec2020); color_space!(HSL); color_space!(HWB); +color_space!(RGB); color_space!(RGBA); macro_rules! predefined { @@ -2659,6 +2706,7 @@ macro_rules! rgb { rgb!(SRGB); rgb!(HSL); rgb!(HWB); +rgb!(RGB); impl From for CssColor { fn from(color: RGBA) -> CssColor { @@ -2747,6 +2795,23 @@ unbounded_color_gamut!(OKLCH, l, c, h); hsl_hwb_color_gamut!(HSL, s, l); hsl_hwb_color_gamut!(HWB, w, b); +impl ColorGamut for RGB { + #[inline] + fn in_gamut(&self) -> bool { + self.r >= 0.0 && self.r <= 255.0 && self.g >= 0.0 && self.g <= 255.0 && self.b >= 0.0 && self.b <= 255.0 + } + + #[inline] + fn clip(&self) -> Self { + Self { + r: self.r.clamp(0.0, 255.0), + g: self.g.clamp(0.0, 255.0), + b: self.b.clamp(0.0, 255.0), + alpha: self.alpha.clamp(0.0, 1.0), + } + } +} + fn delta_eok>(a: T, b: OKLCH) -> f32 { // https://www.w3.org/TR/css-color-4/#color-difference-OK let a: OKLAB = a.into(); diff --git a/src/values/length.rs b/src/values/length.rs index 15fa6efa..c21e5723 100644 --- a/src/values/length.rs +++ b/src/values/length.rs @@ -639,7 +639,7 @@ impl Length { } match (a, b) { - (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(a.add(*b))), + (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(a.add(*b).unwrap())), (Length::Calc(calc), b) => { if let Calc::Value(a) = *calc { a.add(b) diff --git a/src/values/percentage.rs b/src/values/percentage.rs index b1bd8e0c..2712fa50 100644 --- a/src/values/percentage.rs +++ b/src/values/percentage.rs @@ -73,11 +73,13 @@ impl std::convert::Into> for Percentage { } } -impl std::convert::From> for Percentage { - fn from(calc: Calc) -> Percentage { +impl std::convert::TryFrom> for Percentage { + type Error = (); + + fn try_from(calc: Calc) -> Result { match calc { - Calc::Value(v) => *v, - _ => unreachable!(), + Calc::Value(v) => Ok(*v), + _ => Err(()), } } } @@ -226,6 +228,7 @@ impl< + Zero + TrySign + TryFrom + + TryInto + PartialOrd + std::fmt::Debug, > Parse<'i> for DimensionPercentage @@ -372,7 +375,7 @@ impl + Clone + Zero + TrySign + std::fmt::Debug> DimensionPercentag match (a, b) { (DimensionPercentage::Calc(a), DimensionPercentage::Calc(b)) => { - DimensionPercentage::Calc(Box::new(a.add(*b))) + DimensionPercentage::Calc(Box::new(a.add(*b).unwrap())) } (DimensionPercentage::Calc(calc), b) => { if let Calc::Value(a) = *calc { @@ -457,6 +460,17 @@ impl> TryFrom for DimensionPercentage } } +impl> TryInto for DimensionPercentage { + type Error = (); + + fn try_into(self) -> Result { + match self { + DimensionPercentage::Dimension(d) => d.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + impl Zero for DimensionPercentage { fn zero() -> Self { DimensionPercentage::Dimension(D::zero()) diff --git a/src/values/time.rs b/src/values/time.rs index f3e96525..b85d7342 100644 --- a/src/values/time.rs +++ b/src/values/time.rs @@ -129,11 +129,13 @@ impl std::convert::Into> for Time { } } -impl std::convert::From> for Time { - fn from(calc: Calc