Skip to content

Commit 8762921

Browse files
committed
Add color() parsing and serialization
1 parent aa72d31 commit 8762921

File tree

3 files changed

+151
-3
lines changed

3 files changed

+151
-3
lines changed

src/color.rs

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
use std::f32::consts::PI;
65
use std::fmt;
6+
use std::{f32::consts::PI, str::FromStr};
77

88
use super::{BasicParseError, ParseError, Parser, ToCss, Token};
99

@@ -350,6 +350,81 @@ macro_rules! impl_lch_like {
350350
impl_lch_like!(Lch, "lch");
351351
impl_lch_like!(Oklch, "oklch");
352352

353+
#[derive(Clone, Copy, PartialEq, Debug)]
354+
pub enum PredefinedColorSpace {
355+
Srgb,
356+
SrgbLinear,
357+
DisplayP3,
358+
A98Rgb,
359+
ProphotoRgb,
360+
Rec2020,
361+
XyzD50,
362+
XyzD65,
363+
}
364+
365+
impl PredefinedColorSpace {
366+
fn as_str(&self) -> &str {
367+
match self {
368+
PredefinedColorSpace::Srgb => "srgb",
369+
PredefinedColorSpace::SrgbLinear => "srgb-linear",
370+
PredefinedColorSpace::DisplayP3 => "display-p3",
371+
PredefinedColorSpace::A98Rgb => "a98-rgb",
372+
PredefinedColorSpace::ProphotoRgb => "prophoto-rgb",
373+
PredefinedColorSpace::Rec2020 => "rec2020",
374+
PredefinedColorSpace::XyzD50 => "xyz-d50",
375+
PredefinedColorSpace::XyzD65 => "xyz-d65",
376+
}
377+
}
378+
}
379+
380+
#[cfg(feature = "serde")]
381+
impl Serialize for PredefinedColorSpace {
382+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
383+
where
384+
S: Serializer,
385+
{
386+
self.as_str().serialize(serializer)
387+
}
388+
}
389+
390+
impl ToCss for PredefinedColorSpace {
391+
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
392+
where
393+
W: fmt::Write,
394+
{
395+
dest.write_str(self.as_str())
396+
}
397+
}
398+
399+
#[derive(Clone, Copy, PartialEq, Debug)]
400+
pub struct ColorFunction {
401+
pub color_space: PredefinedColorSpace,
402+
pub red: f32,
403+
pub green: f32,
404+
pub blue: f32,
405+
pub alpha: f32,
406+
}
407+
408+
impl ToCss for ColorFunction {
409+
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
410+
where
411+
W: fmt::Write,
412+
{
413+
dest.write_str("color(")?;
414+
self.color_space.to_css(dest)?;
415+
dest.write_str(" ")?;
416+
self.red.to_css(dest)?;
417+
dest.write_str(" ")?;
418+
self.green.to_css(dest)?;
419+
dest.write_str(" ")?;
420+
self.blue.to_css(dest)?;
421+
422+
serialize_alpha(dest, self.alpha, false)?;
423+
424+
dest.write_char(')')
425+
}
426+
}
427+
353428
/// An absolutely specified color.
354429
/// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-base
355430
#[derive(Clone, Copy, PartialEq, Debug)]
@@ -370,6 +445,8 @@ pub enum AbsoluteColor {
370445
/// Specifies an Oklab color by Oklab Lightness, Chroma, and hue using
371446
/// the OKLCH cylindrical coordinate model.
372447
Oklch(Oklch),
448+
/// Specified a sRGB based color with a predefined color space.
449+
ColorFunction(ColorFunction),
373450
}
374451

375452
impl AbsoluteColor {
@@ -381,6 +458,7 @@ impl AbsoluteColor {
381458
Self::Lch(c) => c.alpha,
382459
Self::Oklab(c) => c.alpha,
383460
Self::Oklch(c) => c.alpha,
461+
Self::ColorFunction(c) => c.alpha,
384462
}
385463
}
386464
}
@@ -396,6 +474,7 @@ impl ToCss for AbsoluteColor {
396474
Self::Lch(lch) => lch.to_css(dest),
397475
Self::Oklab(lab) => lab.to_css(dest),
398476
Self::Oklch(lch) => lch.to_css(dest),
477+
Self::ColorFunction(color_function) => color_function.to_css(dest),
399478
}
400479
}
401480
}
@@ -571,7 +650,7 @@ impl Color {
571650
Token::Function(ref name) => {
572651
let name = name.clone();
573652
return input.parse_nested_block(|arguments| {
574-
parse_color_function(component_parser, &*name, arguments)
653+
parse_color(component_parser, &*name, arguments)
575654
});
576655
}
577656
_ => Err(()),
@@ -785,7 +864,7 @@ fn clamp_floor_256_f32(val: f32) -> u8 {
785864
}
786865

787866
#[inline]
788-
fn parse_color_function<'i, 't, ComponentParser>(
867+
fn parse_color<'i, 't, ComponentParser>(
789868
component_parser: &ComponentParser,
790869
name: &str,
791870
arguments: &mut Parser<'i, 't>,
@@ -827,6 +906,8 @@ where
827906
Color::Absolute(AbsoluteColor::Oklch(Oklch::new(l.max(0.), c.max(0.), h, alpha)))
828907
}),
829908

909+
"color" => parse_color_function(component_parser, arguments),
910+
830911
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name.to_owned().into()))),
831912
}?;
832913

@@ -1072,3 +1153,51 @@ where
10721153

10731154
Ok(into_color(lightness, chroma, hue, alpha))
10741155
}
1156+
1157+
#[inline]
1158+
fn parse_color_function<'i, 't, ComponentParser>(
1159+
component_parser: &ComponentParser,
1160+
arguments: &mut Parser<'i, 't>,
1161+
) -> Result<Color, ParseError<'i, ComponentParser::Error>>
1162+
where
1163+
ComponentParser: ColorComponentParser<'i>,
1164+
{
1165+
let location = arguments.current_source_location();
1166+
let color_space = arguments.try_parse(|i| {
1167+
let ident = i.expect_ident()?;
1168+
Ok(match_ignore_ascii_case! {
1169+
ident,
1170+
"srgb" => PredefinedColorSpace::Srgb,
1171+
"srgb-linear" => PredefinedColorSpace::SrgbLinear,
1172+
"display-p3" => PredefinedColorSpace::DisplayP3,
1173+
"a98-rgb" => PredefinedColorSpace::A98Rgb,
1174+
"prophoto-rgb" => PredefinedColorSpace::ProphotoRgb,
1175+
"rec2020" => PredefinedColorSpace::Rec2020,
1176+
"xyz-d50" => PredefinedColorSpace::XyzD50,
1177+
"xyz-d65" => PredefinedColorSpace::XyzD65,
1178+
_ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
1179+
})
1180+
})?;
1181+
1182+
let red = component_parser
1183+
.parse_number_or_percentage(arguments)?
1184+
.unit_value();
1185+
let green = component_parser
1186+
.parse_number_or_percentage(arguments)?
1187+
.unit_value();
1188+
let blue = component_parser
1189+
.parse_number_or_percentage(arguments)?
1190+
.unit_value();
1191+
1192+
let alpha = parse_alpha(component_parser, arguments, false)?;
1193+
1194+
Ok(Color::Absolute(AbsoluteColor::ColorFunction(
1195+
ColorFunction {
1196+
color_space,
1197+
red,
1198+
green,
1199+
blue,
1200+
alpha,
1201+
},
1202+
)))
1203+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
"color(srgb 1 1 1 / 0.5)", "color(srgb 1 1 1 / 0.5)"
3+
]

src/tests.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,18 @@ fn color4_lab_lch_oklab_oklch() {
404404
)
405405
}
406406

407+
#[test]
408+
fn color4_color_function() {
409+
run_color_tests(
410+
include_str!("css-parsing-tests/color4_color_function.json"),
411+
|c| {
412+
c.ok()
413+
.map(|v| v.to_css_string().to_json())
414+
.unwrap_or(Value::Null)
415+
},
416+
)
417+
}
418+
407419
#[test]
408420
fn nth() {
409421
run_json_tests(include_str!("css-parsing-tests/An+B.json"), |input| {
@@ -854,6 +866,7 @@ where
854866
}
855867
}
856868

869+
#[cfg(feature = "serde")]
857870
impl ToJson for Color {
858871
fn to_json(&self) -> Value {
859872
match *self {
@@ -866,6 +879,9 @@ impl ToJson for Color {
866879
AbsoluteColor::Lch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]),
867880
AbsoluteColor::Oklab(ref c) => json!([c.lightness, c.a, c.b, c.alpha]),
868881
AbsoluteColor::Oklch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]),
882+
AbsoluteColor::ColorFunction(ref c) => {
883+
json!([c.color_space, c.red, c.green, c.blue, c.alpha])
884+
}
869885
},
870886
}
871887
}

0 commit comments

Comments
 (0)