From 901c911b5b6f80f55d52e5d5bfdb3eff0735191f Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 9 Feb 2020 10:41:05 -0500 Subject: [PATCH] Handle percentages and large/small values inside `rgb()` and `rgba()` --- src/builtin/color.rs | 40 ++++++++++++++++++++++++++++++++-------- src/color/mod.rs | 18 +++++++++++++++++- src/value/mod.rs | 22 ---------------------- tests/color.rs | 10 ++++++++++ 4 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/builtin/color.rs b/src/builtin/color.rs index 225f859..1922c35 100644 --- a/src/builtin/color.rs +++ b/src/builtin/color.rs @@ -13,10 +13,22 @@ pub(crate) fn register(f: &mut BTreeMap) { decl!(f "rgb", |args, _| { let channels = args.get("channels").unwrap_or(&Value::Null); if channels.is_null() { - let red: u16 = arg!(args, 0, "red").clone().try_into().unwrap(); - let green: u16 = arg!(args, 1, "green").clone().try_into().unwrap(); - let blue: u16 = arg!(args, 2, "blue").clone().try_into().unwrap(); - Some(Value::Color(Color::from_values(red, green, blue, Number::from(1)))) + let red = match arg!(args, 0, "red").clone().eval() { + Value::Dimension(n, Unit::None) => n, + Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + _ => todo!("expected either unitless or % number for alpha"), + }; + let green = match arg!(args, 1, "green").clone().eval() { + Value::Dimension(n, Unit::None) => n, + Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + _ => todo!("expected either unitless or % number for alpha"), + }; + let blue = match arg!(args, 2, "blue").clone().eval() { + Value::Dimension(n, Unit::None) => n, + Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + _ => todo!("expected either unitless or % number for alpha"), + }; + Some(Value::Color(Color::from_rgba(red, green, blue, Number::from(1)))) } else { todo!("channels variable in `rgb`") } @@ -24,15 +36,27 @@ pub(crate) fn register(f: &mut BTreeMap) { decl!(f "rgba", |args, _| { let channels = args.get("channels").unwrap_or(&Value::Null); if channels.is_null() { - let red: u16 = arg!(args, 0, "red").clone().try_into().unwrap(); - let green: u16 = arg!(args, 1, "green").clone().try_into().unwrap(); - let blue: u16 = arg!(args, 2, "blue").clone().try_into().unwrap(); + let red = match arg!(args, 0, "red").clone().eval() { + Value::Dimension(n, Unit::None) => n, + Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + _ => todo!("expected either unitless or % number for alpha"), + }; + let green = match arg!(args, 1, "green").clone().eval() { + Value::Dimension(n, Unit::None) => n, + Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + _ => todo!("expected either unitless or % number for alpha"), + }; + let blue = match arg!(args, 2, "blue").clone().eval() { + Value::Dimension(n, Unit::None) => n, + Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), + _ => todo!("expected either unitless or % number for alpha"), + }; let alpha = match arg!(args, 3, "alpha").clone().eval() { Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::Percent) => n / Number::from(100), _ => todo!("expected either unitless or % number for alpha"), }; - Some(Value::Color(Color::from_values(red, green, blue, alpha))) + Some(Value::Color(Color::from_rgba(red, green, blue, alpha))) } else { todo!("channels variable in `rgba`") } diff --git a/src/color/mod.rs b/src/color/mod.rs index 8487148..6c7319b 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -144,7 +144,23 @@ impl Color { } } - pub fn from_values(red: u16, green: u16, blue: u16, alpha: Number) -> Self { + pub fn from_rgba(red: Number, green: Number, blue: Number, alpha: Number) -> Self { + macro_rules! clamp { + ($channel:ident) => { + let $channel = if $channel > Number::from(255) { + 255_u16 + } else if $channel < Number::from(0) { + 0_u16 + } else { + $channel.round().to_integer().to_u16().unwrap() + }; + }; + } + + clamp!(red); + clamp!(green); + clamp!(blue); + let repr = if alpha < Number::from(1) { format!("rgba({}, {}, {}, {})", red, green, blue, alpha) } else if let Ok(c) = ColorName::try_from([red, green, blue]) { diff --git a/src/value/mod.rs b/src/value/mod.rs index 2466b10..2a19a54 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,10 +1,7 @@ #![allow(dead_code, unused_variables)] -use std::convert::TryInto; use std::fmt::{self, Display}; use std::iter::Iterator; -use num_bigint::BigInt; - use crate::color::Color; use crate::common::{ListSeparator, Op, QuoteKind}; use crate::units::Unit; @@ -52,25 +49,6 @@ impl Display for Value { } } -impl TryInto for Value { - type Error = &'static str; - fn try_into(self) -> Result { - match self { - Self::BinaryOp(..) => self.eval().try_into(), - Self::Dimension(n, Unit::Percent) => todo!(), - Self::Dimension(n, Unit::None) => { - if n >= Number::from(BigInt::from(255)) { - Ok(255) - } else { - Ok(n.to_integer().to_str_radix(10).parse().unwrap()) - } - } - Self::Dimension(n, _) => Err("Expected `val` to have no units or \"%\"."), - _ => Err("expected number"), - } - } -} - impl Value { pub fn is_null(&self) -> bool { self == &Value::Null diff --git a/tests/color.rs b/tests/color.rs index 8779888..214eb86 100644 --- a/tests/color.rs +++ b/tests/color.rs @@ -46,6 +46,16 @@ test!( "a {\n color: rgb(1, 2, 3);\n}\n", "a {\n color: #010203;\n}\n" ); +test!( + rgba_percent, + "a {\n color: rgba(159%, 169, 169%, 50%);\n}\n", + "a {\n color: rgba(255, 169, 255, 0.5);\n}\n" +); +test!( + rgba_percent_round_up, + "a {\n color: rgba(59%, 169, 69%, 50%);\n}\n", + "a {\n color: rgba(150, 169, 176, 0.5);\n}\n" +); test!( rgb_double_digits, "a {\n color: rgb(254, 255, 255);\n}\n",