Skip to content

Commit a09f283

Browse files
authored
Enhance modern component serialization with inf and nan support (#351)
* Enhance component serialization with inf and nan support Modern syntax colors support NaN and infinity values. These values are now allowed, where previously for some lab/lch/oklab/oklch components, NaN values were truncated to 0.0. NaN and infinity now also correctly serializes to calc(NaN) and calc(infinity) respectively. * Do not clamp or adjust any values when parsing color.
1 parent 2fd15ba commit a09f283

File tree

3 files changed

+2026
-1966
lines changed

3 files changed

+2026
-1966
lines changed

src/color.rs

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
1616

1717
const OPAQUE: f32 = 1.0;
1818

19-
fn serialize_none_or<T>(dest: &mut impl fmt::Write, value: &Option<T>) -> fmt::Result
20-
where
21-
T: ToCss,
22-
{
23-
match value {
24-
Some(v) => v.to_css(dest),
25-
None => dest.write_str("none"),
26-
}
27-
}
28-
2919
/// Serialize the alpha copmonent of a color according to the specification.
3020
/// <https://drafts.csswg.org/css-color-4/#serializing-alpha-values>
3121
#[inline]
@@ -55,6 +45,34 @@ pub fn serialize_color_alpha(
5545
rounded_alpha.to_css(dest)
5646
}
5747

48+
/// A [`ModernComponent`] can serialize to `none`, `nan`, `infinity` and
49+
/// floating point values.
50+
struct ModernComponent<'a>(&'a Option<f32>);
51+
52+
impl<'a> ToCss for ModernComponent<'a> {
53+
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
54+
where
55+
W: fmt::Write,
56+
{
57+
if let Some(value) = self.0 {
58+
if value.is_finite() {
59+
value.to_css(dest)
60+
} else if value.is_nan() {
61+
dest.write_str("calc(NaN)")
62+
} else {
63+
debug_assert!(value.is_infinite());
64+
if value.is_sign_negative() {
65+
dest.write_str("calc(-infinity)")
66+
} else {
67+
dest.write_str("calc(infinity)")
68+
}
69+
}
70+
} else {
71+
dest.write_str("none")
72+
}
73+
}
74+
}
75+
5876
// Guaratees hue in [0..360)
5977
fn normalize_hue(hue: f32) -> f32 {
6078
// <https://drafts.csswg.org/css-values/#angles>
@@ -363,11 +381,11 @@ macro_rules! impl_lab_like {
363381
{
364382
dest.write_str($fname)?;
365383
dest.write_str("(")?;
366-
serialize_none_or(dest, &self.lightness)?;
384+
ModernComponent(&self.lightness).to_css(dest)?;
367385
dest.write_char(' ')?;
368-
serialize_none_or(dest, &self.a)?;
386+
ModernComponent(&self.a).to_css(dest)?;
369387
dest.write_char(' ')?;
370-
serialize_none_or(dest, &self.b)?;
388+
ModernComponent(&self.b).to_css(dest)?;
371389
serialize_color_alpha(dest, self.alpha, false)?;
372390
dest.write_char(')')
373391
}
@@ -454,11 +472,11 @@ macro_rules! impl_lch_like {
454472
{
455473
dest.write_str($fname)?;
456474
dest.write_str("(")?;
457-
serialize_none_or(dest, &self.lightness)?;
475+
ModernComponent(&self.lightness).to_css(dest)?;
458476
dest.write_char(' ')?;
459-
serialize_none_or(dest, &self.chroma)?;
477+
ModernComponent(&self.chroma).to_css(dest)?;
460478
dest.write_char(' ')?;
461-
serialize_none_or(dest, &self.hue)?;
479+
ModernComponent(&self.hue).to_css(dest)?;
462480
serialize_color_alpha(dest, self.alpha, false)?;
463481
dest.write_char(')')
464482
}
@@ -579,11 +597,11 @@ impl ToCss for ColorFunction {
579597
dest.write_str("color(")?;
580598
self.color_space.to_css(dest)?;
581599
dest.write_char(' ')?;
582-
serialize_none_or(dest, &self.c1)?;
600+
ModernComponent(&self.c1).to_css(dest)?;
583601
dest.write_char(' ')?;
584-
serialize_none_or(dest, &self.c2)?;
602+
ModernComponent(&self.c2).to_css(dest)?;
585603
dest.write_char(' ')?;
586-
serialize_none_or(dest, &self.c3)?;
604+
ModernComponent(&self.c3).to_css(dest)?;
587605

588606
serialize_color_alpha(dest, self.alpha, false)?;
589607

@@ -1495,7 +1513,7 @@ where
14951513
P::parse_number_or_percentage,
14961514
)?;
14971515

1498-
let lightness = lightness.map(|l| l.value(lightness_range).max(0.0));
1516+
let lightness = lightness.map(|l| l.value(lightness_range));
14991517
let a = a.map(|a| a.value(a_b_range));
15001518
let b = b.map(|b| b.value(a_b_range));
15011519

@@ -1521,8 +1539,8 @@ where
15211539
P::parse_angle_or_number,
15221540
)?;
15231541

1524-
let lightness = lightness.map(|l| l.value(lightness_range).max(0.0));
1525-
let chroma = chroma.map(|c| c.value(chroma_range).max(0.0));
1542+
let lightness = lightness.map(|l| l.value(lightness_range));
1543+
let chroma = chroma.map(|c| c.value(chroma_range));
15261544
let hue = hue.map(|h| normalize_hue(h.degrees()));
15271545

15281546
Ok(into_color(lightness, chroma, hue, alpha))
@@ -1591,3 +1609,45 @@ where
15911609

15921610
Ok((r1, r2, r3, alpha))
15931611
}
1612+
1613+
#[cfg(test)]
1614+
mod tests {
1615+
use super::*;
1616+
1617+
#[test]
1618+
fn serialize_modern_components() {
1619+
// None.
1620+
assert_eq!(ModernComponent(&None).to_css_string(), "none".to_string());
1621+
1622+
// Finite values.
1623+
assert_eq!(
1624+
ModernComponent(&Some(10.0)).to_css_string(),
1625+
"10".to_string()
1626+
);
1627+
assert_eq!(
1628+
ModernComponent(&Some(-10.0)).to_css_string(),
1629+
"-10".to_string()
1630+
);
1631+
assert_eq!(ModernComponent(&Some(0.0)).to_css_string(), "0".to_string());
1632+
assert_eq!(
1633+
ModernComponent(&Some(-0.0)).to_css_string(),
1634+
"0".to_string()
1635+
);
1636+
1637+
// Infinite values.
1638+
assert_eq!(
1639+
ModernComponent(&Some(f32::INFINITY)).to_css_string(),
1640+
"calc(infinity)".to_string()
1641+
);
1642+
assert_eq!(
1643+
ModernComponent(&Some(f32::NEG_INFINITY)).to_css_string(),
1644+
"calc(-infinity)".to_string()
1645+
);
1646+
1647+
// NaN.
1648+
assert_eq!(
1649+
ModernComponent(&Some(f32::NAN)).to_css_string(),
1650+
"calc(NaN)".to_string()
1651+
);
1652+
}
1653+
}

0 commit comments

Comments
 (0)