Skip to content

Commit 9bdf3bc

Browse files
committed
Use Option<f32> for rgb components as well.
1 parent a2013cd commit 9bdf3bc

File tree

2 files changed

+103
-114
lines changed

2 files changed

+103
-114
lines changed

src/color.rs

+89-105
Original file line numberDiff line numberDiff line change
@@ -63,101 +63,49 @@ fn normalize_hue(hue: f32) -> f32 {
6363
#[repr(C)]
6464
pub struct RGBA {
6565
/// The red component.
66-
pub red: u8,
66+
pub red: Option<u8>,
6767
/// The green component.
68-
pub green: u8,
68+
pub green: Option<u8>,
6969
/// The blue component.
70-
pub blue: u8,
70+
pub blue: Option<u8>,
7171
/// The alpha component.
72-
pub alpha: f32,
72+
pub alpha: Option<f32>,
7373
}
7474

7575
impl RGBA {
7676
/// Constructs a new RGBA value from float components. It expects the red,
7777
/// green, blue and alpha channels in that order, and all values will be
7878
/// clamped to the 0.0 ... 1.0 range.
7979
#[inline]
80-
pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
80+
pub fn from_floats(
81+
red: Option<f32>,
82+
green: Option<f32>,
83+
blue: Option<f32>,
84+
alpha: Option<f32>,
85+
) -> Self {
8186
Self::new(
82-
clamp_unit_f32(red),
83-
clamp_unit_f32(green),
84-
clamp_unit_f32(blue),
85-
alpha.max(0.0).min(1.0),
87+
red.map(|r| clamp_unit_f32(r)),
88+
green.map(|g| clamp_unit_f32(g)),
89+
blue.map(|b| clamp_unit_f32(b)),
90+
alpha.map(|a| a.clamp(0.0, OPAQUE)),
8691
)
8792
}
8893

89-
/// Returns a transparent color.
90-
#[inline]
91-
pub fn transparent() -> Self {
92-
Self::new(0, 0, 0, 0.0)
93-
}
94-
9594
/// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
9695
#[inline]
97-
pub const fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
96+
pub const fn new(
97+
red: Option<u8>,
98+
green: Option<u8>,
99+
blue: Option<u8>,
100+
alpha: Option<f32>,
101+
) -> Self {
98102
Self {
99103
red,
100104
green,
101105
blue,
102106
alpha,
103107
}
104108
}
105-
106-
/// Returns the red channel in a floating point number form, from 0 to 1.
107-
#[inline]
108-
pub fn red_f32(&self) -> f32 {
109-
self.red as f32 / 255.0
110-
}
111-
112-
/// Returns the green channel in a floating point number form, from 0 to 1.
113-
#[inline]
114-
pub fn green_f32(&self) -> f32 {
115-
self.green as f32 / 255.0
116-
}
117-
118-
/// Returns the blue channel in a floating point number form, from 0 to 1.
119-
#[inline]
120-
pub fn blue_f32(&self) -> f32 {
121-
self.blue as f32 / 255.0
122-
}
123-
124-
/// Returns the alpha channel in a floating point number form, from 0 to 1.
125-
#[inline]
126-
pub fn alpha_f32(&self) -> f32 {
127-
self.alpha
128-
}
129-
130-
/// Parse a color hash, without the leading '#' character.
131-
#[inline]
132-
pub fn parse_hash(value: &[u8]) -> Result<Self, ()> {
133-
Ok(match value.len() {
134-
8 => Self::new(
135-
from_hex(value[0])? * 16 + from_hex(value[1])?,
136-
from_hex(value[2])? * 16 + from_hex(value[3])?,
137-
from_hex(value[4])? * 16 + from_hex(value[5])?,
138-
(from_hex(value[6])? * 16 + from_hex(value[7])?) as f32 / 255.0,
139-
),
140-
6 => Self::new(
141-
from_hex(value[0])? * 16 + from_hex(value[1])?,
142-
from_hex(value[2])? * 16 + from_hex(value[3])?,
143-
from_hex(value[4])? * 16 + from_hex(value[5])?,
144-
OPAQUE,
145-
),
146-
4 => Self::new(
147-
from_hex(value[0])? * 17,
148-
from_hex(value[1])? * 17,
149-
from_hex(value[2])? * 17,
150-
(from_hex(value[3])? * 17) as f32 / 255.0,
151-
),
152-
3 => Self::new(
153-
from_hex(value[0])? * 17,
154-
from_hex(value[1])? * 17,
155-
from_hex(value[2])? * 17,
156-
OPAQUE,
157-
),
158-
_ => return Err(()),
159-
})
160-
}
161109
}
162110

163111
#[cfg(feature = "serde")]
@@ -186,17 +134,17 @@ impl ToCss for RGBA {
186134
where
187135
W: fmt::Write,
188136
{
189-
let has_alpha = self.alpha != OPAQUE;
137+
let has_alpha = self.alpha.unwrap_or(0.0) != OPAQUE;
190138

191139
dest.write_str(if has_alpha { "rgba(" } else { "rgb(" })?;
192-
self.red.to_css(dest)?;
140+
self.red.unwrap_or(0).to_css(dest)?;
193141
dest.write_str(", ")?;
194-
self.green.to_css(dest)?;
142+
self.green.unwrap_or(0).to_css(dest)?;
195143
dest.write_str(", ")?;
196-
self.blue.to_css(dest)?;
144+
self.blue.unwrap_or(0).to_css(dest)?;
197145

198146
// Legacy syntax does not allow none components.
199-
serialize_alpha(dest, Some(self.alpha), true)?;
147+
serialize_alpha(dest, Some(self.alpha.unwrap_or(0.0)), true)?;
200148

201149
dest.write_char(')')
202150
}
@@ -242,7 +190,7 @@ impl ToCss for Hsl {
242190
self.lightness.unwrap_or(0.0),
243191
);
244192

245-
RGBA::from_floats(red, green, blue, self.alpha.unwrap_or(0.0)).to_css(dest)
193+
RGBA::from_floats(Some(red), Some(green), Some(blue), self.alpha).to_css(dest)
246194
}
247195
}
248196

@@ -307,7 +255,7 @@ impl ToCss for Hwb {
307255
self.blackness.unwrap_or(0.0),
308256
);
309257

310-
RGBA::from_floats(red, green, blue, self.alpha.unwrap_or(0.0)).to_css(dest)
258+
RGBA::from_floats(Some(red), Some(green), Some(blue), self.alpha).to_css(dest)
311259
}
312260
}
313261

@@ -836,7 +784,7 @@ pub trait FromParsedColor {
836784
fn from_current_color() -> Self;
837785

838786
/// Construct a new color from red, green, blue and alpha components.
839-
fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self;
787+
fn from_rgba(red: Option<u8>, green: Option<u8>, blue: Option<u8>, alpha: Option<f32>) -> Self;
840788

841789
/// Construct a new color from hue, saturation, lightness and alpha components.
842790
fn from_hsl(
@@ -892,6 +840,42 @@ pub trait FromParsedColor {
892840
) -> Self;
893841
}
894842

843+
/// Parse a color hash, without the leading '#' character.
844+
#[inline]
845+
846+
pub fn parse_hash_color<'i, 't, P>(value: &[u8]) -> Result<P::Output, ()>
847+
where
848+
P: ColorParser<'i>,
849+
{
850+
Ok(match value.len() {
851+
8 => P::Output::from_rgba(
852+
Some(from_hex(value[0])? * 16 + from_hex(value[1])?),
853+
Some(from_hex(value[2])? * 16 + from_hex(value[3])?),
854+
Some(from_hex(value[4])? * 16 + from_hex(value[5])?),
855+
Some((from_hex(value[6])? * 16 + from_hex(value[7])?) as f32 / 255.0),
856+
),
857+
6 => P::Output::from_rgba(
858+
Some(from_hex(value[0])? * 16 + from_hex(value[1])?),
859+
Some(from_hex(value[2])? * 16 + from_hex(value[3])?),
860+
Some(from_hex(value[4])? * 16 + from_hex(value[5])?),
861+
Some(OPAQUE),
862+
),
863+
4 => P::Output::from_rgba(
864+
Some(from_hex(value[0])? * 17),
865+
Some(from_hex(value[1])? * 17),
866+
Some(from_hex(value[2])? * 17),
867+
Some((from_hex(value[3])? * 17) as f32 / 255.0),
868+
),
869+
3 => P::Output::from_rgba(
870+
Some(from_hex(value[0])? * 17),
871+
Some(from_hex(value[1])? * 17),
872+
Some(from_hex(value[2])? * 17),
873+
Some(OPAQUE),
874+
),
875+
_ => return Err(()),
876+
})
877+
}
878+
895879
/// Parse a CSS color with the specified [`ColorComponentParser`] and return a
896880
/// new color value on success.
897881
pub fn parse_color_with<'i, 't, P>(
@@ -904,8 +888,9 @@ where
904888
let location = input.current_source_location();
905889
let token = input.next()?;
906890
match *token {
907-
Token::Hash(ref value) | Token::IDHash(ref value) => RGBA::parse_hash(value.as_bytes())
908-
.map(|rgba| P::Output::from_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha)),
891+
Token::Hash(ref value) | Token::IDHash(ref value) => {
892+
parse_hash_color::<P>(value.as_bytes())
893+
}
909894
Token::Ident(ref value) => parse_color_keyword(&*value),
910895
Token::Function(ref name) => {
911896
let name = name.clone();
@@ -925,7 +910,7 @@ impl FromParsedColor for Color {
925910
}
926911

927912
#[inline]
928-
fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
913+
fn from_rgba(red: Option<u8>, green: Option<u8>, blue: Option<u8>, alpha: Option<f32>) -> Self {
929914
Color::Rgba(RGBA::new(red, green, blue, alpha))
930915
}
931916

@@ -1164,10 +1149,10 @@ where
11641149
}
11651150

11661151
match_ignore_ascii_case! { ident ,
1167-
"transparent" => Ok(Output::from_rgba(0, 0, 0, 0.0)),
1152+
"transparent" => Ok(Output::from_rgba(Some(0), Some(0), Some(0), Some(0.0))),
11681153
"currentcolor" => Ok(Output::from_current_color()),
11691154
_ => keyword(ident)
1170-
.map(|(r, g, b)| Output::from_rgba(*r, *g, *b, 1.0))
1155+
.map(|(r, g, b)| Output::from_rgba(Some(*r), Some(*g), Some(*b), Some(1.0)))
11711156
.ok_or(()),
11721157
}
11731158
}
@@ -1317,48 +1302,47 @@ where
13171302
// are parsing the legacy syntax.
13181303
let is_legacy_syntax = maybe_red.is_some() && arguments.try_parse(|p| p.expect_comma()).is_ok();
13191304

1320-
let red: u8;
1321-
let green: u8;
1322-
let blue: u8;
1305+
let red: Option<u8>;
1306+
let green: Option<u8>;
1307+
let blue: Option<u8>;
13231308

13241309
let alpha = if is_legacy_syntax {
13251310
match maybe_red.unwrap() {
13261311
NumberOrPercentage::Number { value } => {
1327-
red = clamp_floor_256_f32(value);
1328-
green = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
1312+
red = Some(clamp_floor_256_f32(value));
1313+
green = Some(clamp_floor_256_f32(color_parser.parse_number(arguments)?));
13291314
arguments.expect_comma()?;
1330-
blue = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
1315+
blue = Some(clamp_floor_256_f32(color_parser.parse_number(arguments)?));
13311316
}
13321317
NumberOrPercentage::Percentage { unit_value } => {
1333-
red = clamp_unit_f32(unit_value);
1334-
green = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
1318+
red = Some(clamp_unit_f32(unit_value));
1319+
green = Some(clamp_unit_f32(color_parser.parse_percentage(arguments)?));
13351320
arguments.expect_comma()?;
1336-
blue = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
1321+
blue = Some(clamp_unit_f32(color_parser.parse_percentage(arguments)?));
13371322
}
13381323
}
13391324

1340-
parse_legacy_alpha(color_parser, arguments)?
1325+
Some(parse_legacy_alpha(color_parser, arguments)?)
13411326
} else {
13421327
#[inline]
1343-
fn get_component_value(c: &Option<NumberOrPercentage>) -> u8 {
1344-
match *c {
1345-
Some(NumberOrPercentage::Number { value }) => clamp_floor_256_f32(value),
1346-
Some(NumberOrPercentage::Percentage { unit_value }) => clamp_unit_f32(unit_value),
1347-
None => 0,
1348-
}
1328+
fn get_component_value(c: Option<NumberOrPercentage>) -> Option<u8> {
1329+
c.map(|c| match c {
1330+
NumberOrPercentage::Number { value } => clamp_floor_256_f32(value),
1331+
NumberOrPercentage::Percentage { unit_value } => clamp_unit_f32(unit_value),
1332+
})
13491333
}
13501334

1351-
red = get_component_value(&maybe_red);
1335+
red = get_component_value(maybe_red);
13521336

1353-
green = get_component_value(&parse_none_or(arguments, |p| {
1337+
green = get_component_value(parse_none_or(arguments, |p| {
13541338
color_parser.parse_number_or_percentage(p)
13551339
})?);
13561340

1357-
blue = get_component_value(&parse_none_or(arguments, |p| {
1341+
blue = get_component_value(parse_none_or(arguments, |p| {
13581342
color_parser.parse_number_or_percentage(p)
13591343
})?);
13601344

1361-
parse_modern_alpha(color_parser, arguments)?.unwrap_or(0.0)
1345+
parse_modern_alpha(color_parser, arguments)?
13621346
};
13631347

13641348
Ok(P::Output::from_rgba(red, green, blue, alpha))

src/tests.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -592,19 +592,19 @@ fn serialize_current_color() {
592592

593593
#[test]
594594
fn serialize_rgb_full_alpha() {
595-
let c = Color::Rgba(RGBA::new(255, 230, 204, 1.0));
595+
let c = Color::Rgba(RGBA::new(Some(255), Some(230), Some(204), Some(1.0)));
596596
assert_eq!(c.to_css_string(), "rgb(255, 230, 204)");
597597
}
598598

599599
#[test]
600600
fn serialize_rgba() {
601-
let c = Color::Rgba(RGBA::new(26, 51, 77, 0.125));
601+
let c = Color::Rgba(RGBA::new(Some(26), Some(51), Some(77), Some(0.125)));
602602
assert_eq!(c.to_css_string(), "rgba(26, 51, 77, 0.125)");
603603
}
604604

605605
#[test]
606606
fn serialize_rgba_two_digit_float_if_roundtrips() {
607-
let c = Color::Rgba(RGBA::from_floats(0., 0., 0., 0.5));
607+
let c = Color::Rgba(RGBA::from_floats(Some(0.), Some(0.), Some(0.), Some(0.5)));
608608
assert_eq!(c.to_css_string(), "rgba(0, 0, 0, 0.5)");
609609
}
610610

@@ -1518,7 +1518,7 @@ fn generic_parser() {
15181518
#[derive(Debug, PartialEq)]
15191519
enum OutputType {
15201520
CurrentColor,
1521-
Rgba(u8, u8, u8, f32),
1521+
Rgba(Option<u8>, Option<u8>, Option<u8>, Option<f32>),
15221522
Hsl(Option<f32>, Option<f32>, Option<f32>, Option<f32>),
15231523
Hwb(Option<f32>, Option<f32>, Option<f32>, Option<f32>),
15241524
Lab(Option<f32>, Option<f32>, Option<f32>, Option<f32>),
@@ -1539,7 +1539,12 @@ fn generic_parser() {
15391539
OutputType::CurrentColor
15401540
}
15411541

1542-
fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
1542+
fn from_rgba(
1543+
red: Option<u8>,
1544+
green: Option<u8>,
1545+
blue: Option<u8>,
1546+
alpha: Option<f32>,
1547+
) -> Self {
15431548
OutputType::Rgba(red, green, blue, alpha)
15441549
}
15451550

@@ -1617,10 +1622,10 @@ fn generic_parser() {
16171622
#[rustfmt::skip]
16181623
const TESTS: &[(&str, OutputType)] = &[
16191624
("currentColor", OutputType::CurrentColor),
1620-
("rgb(1, 2, 3)", OutputType::Rgba(1, 2, 3, 1.0)),
1621-
("rgba(1, 2, 3, 0.4)", OutputType::Rgba(1, 2, 3, 0.4)),
1622-
("rgb(none none none / none)", OutputType::Rgba(0, 0, 0, 0.0)),
1623-
("rgb(1 none 3 / none)", OutputType::Rgba(1, 0, 3, 0.0)),
1625+
("rgb(1, 2, 3)", OutputType::Rgba(Some(1), Some(2), Some(3), Some(1.0))),
1626+
("rgba(1, 2, 3, 0.4)", OutputType::Rgba(Some(1), Some(2), Some(3), Some(0.4))),
1627+
("rgb(none none none / none)", OutputType::Rgba(None, None, None, None)),
1628+
("rgb(1 none 3 / none)", OutputType::Rgba(Some(1), None, Some(3), None)),
16241629

16251630
("hsla(45deg, 20%, 30%, 0.4)", OutputType::Hsl(Some(45.0), Some(0.2), Some(0.3), Some(0.4))),
16261631
("hsl(45deg none none)", OutputType::Hsl(Some(45.0), None, None, Some(1.0))),

0 commit comments

Comments
 (0)