Skip to main content

css_ast/functions/
relative_color.rs

1use super::prelude::*;
2use crate::{AngleOrNumber, NoneOr, NumberOrPercentage, NumericValue};
3use css_parse::Box;
4
5/// The `from <color>` clause in relative color syntax.
6///
7/// <https://drafts.csswg.org/css-color-5/#relative-colors>
8///
9/// ```text,ignore
10/// from <color>
11/// ```
12#[node]
13#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
15#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
16#[derive(csskit_derives::NodeWithMetadata)]
17pub struct RelativeColorOrigin<'a> {
18	#[atom(CssAtomSet::From)]
19	pub from_keyword: T![Ident],
20	pub color: Color<'a>,
21}
22
23/// Channel keyword for `rgb()` / `rgba()` relative color syntax.
24///
25/// Valid keywords: `r`, `g`, `b`, `alpha`
26#[node]
27#[derive(
28	Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
29)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
31#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
32#[derive(csskit_derives::NodeWithMetadata)]
33pub enum RgbChannelKeyword {
34	#[atom(CssAtomSet::R)]
35	R(T![Ident]),
36	#[atom(CssAtomSet::G)]
37	G(T![Ident]),
38	#[atom(CssAtomSet::B)]
39	B(T![Ident]),
40	#[atom(CssAtomSet::Alpha)]
41	Alpha(T![Ident]),
42}
43
44/// Channel keyword for `hsl()` / `hsla()` relative color syntax.
45///
46/// Valid keywords: `h`, `s`, `l`, `alpha`
47#[node]
48#[derive(
49	Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
50)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
52#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
53#[derive(csskit_derives::NodeWithMetadata)]
54pub enum HslChannelKeyword {
55	#[atom(CssAtomSet::H)]
56	H(T![Ident]),
57	#[atom(CssAtomSet::S)]
58	S(T![Ident]),
59	#[atom(CssAtomSet::L)]
60	L(T![Ident]),
61	#[atom(CssAtomSet::Alpha)]
62	Alpha(T![Ident]),
63}
64
65/// Channel keyword for `hwb()` relative color syntax.
66///
67/// Valid keywords: `h`, `w`, `b`, `alpha`
68#[node]
69#[derive(
70	Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
71)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
73#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
74#[derive(csskit_derives::NodeWithMetadata)]
75pub enum HwbChannelKeyword {
76	#[atom(CssAtomSet::H)]
77	H(T![Ident]),
78	#[atom(CssAtomSet::W)]
79	W(T![Ident]),
80	#[atom(CssAtomSet::B)]
81	B(T![Ident]),
82	#[atom(CssAtomSet::Alpha)]
83	Alpha(T![Ident]),
84}
85
86/// Channel keyword for `lab()` / `oklab()` relative color syntax.
87///
88/// Valid keywords: `l`, `a`, `b`, `alpha`
89#[node]
90#[derive(
91	Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
92)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
94#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
95#[derive(csskit_derives::NodeWithMetadata)]
96pub enum LabChannelKeyword {
97	#[atom(CssAtomSet::L)]
98	L(T![Ident]),
99	#[atom(CssAtomSet::A)]
100	A(T![Ident]),
101	#[atom(CssAtomSet::B)]
102	B(T![Ident]),
103	#[atom(CssAtomSet::Alpha)]
104	Alpha(T![Ident]),
105}
106
107/// Channel keyword for `lch()` / `oklch()` relative color syntax.
108///
109/// Valid keywords: `l`, `c`, `h`, `alpha`
110#[node]
111#[derive(
112	Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
113)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
115#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
116#[derive(csskit_derives::NodeWithMetadata)]
117pub enum LchChannelKeyword {
118	#[atom(CssAtomSet::L)]
119	L(T![Ident]),
120	#[atom(CssAtomSet::C)]
121	C(T![Ident]),
122	#[atom(CssAtomSet::H)]
123	H(T![Ident]),
124	#[atom(CssAtomSet::Alpha)]
125	Alpha(T![Ident]),
126}
127
128/// Channel keyword for `color()` relative color syntax with xyz spaces.
129///
130/// Valid keywords: `x`, `y`, `z`, `alpha`
131#[node]
132#[derive(
133	Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
134)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
136#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
137#[derive(csskit_derives::NodeWithMetadata)]
138pub enum XyzChannelKeyword {
139	#[atom(CssAtomSet::X)]
140	X(T![Ident]),
141	#[atom(CssAtomSet::Y)]
142	Y(T![Ident]),
143	#[atom(CssAtomSet::Z)]
144	Z(T![Ident]),
145	#[atom(CssAtomSet::Alpha)]
146	Alpha(T![Ident]),
147}
148
149/// Channel keyword for `color()` relative color syntax - union of predefined-rgb and xyz keywords.
150///
151/// Valid keywords: `r`, `g`, `b`, `x`, `y`, `z`, `alpha`
152#[node]
153#[derive(
154	Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
155)]
156#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
157#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
158#[derive(csskit_derives::NodeWithMetadata)]
159pub enum ColorChannelKeyword {
160	#[atom(CssAtomSet::R)]
161	R(T![Ident]),
162	#[atom(CssAtomSet::G)]
163	G(T![Ident]),
164	#[atom(CssAtomSet::B)]
165	B(T![Ident]),
166	#[atom(CssAtomSet::X)]
167	X(T![Ident]),
168	#[atom(CssAtomSet::Y)]
169	Y(T![Ident]),
170	#[atom(CssAtomSet::Z)]
171	Z(T![Ident]),
172	#[atom(CssAtomSet::Alpha)]
173	Alpha(T![Ident]),
174}
175
176/// A channel value in relative color syntax that can be a number, percentage,
177/// `none`, a channel keyword, or a math function containing channel keywords.
178///
179/// For rgb(): `<number> | <percentage> | none | r | g | b | alpha | <calc()>`
180/// For hsl(): `<hue> | <number> | <percentage> | none | h | s | l | alpha | <calc()>`
181/// etc.
182#[node]
183#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
185#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
186#[derive(csskit_derives::NodeWithMetadata)]
187pub enum RelativeChannelValue<'a, K> {
188	Keyword(K),
189	Value(NoneOr<NumericValue<'a, NumberOrPercentage>>),
190}
191
192impl<'a, K: Peek<'a> + Parse<'a>> Peek<'a> for RelativeChannelValue<'a, K> {
193	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
194	where
195		I: Iterator<Item = Cursor> + Clone,
196	{
197		K::peek(p, c) || NoneOr::<NumericValue<NumberOrPercentage>>::peek(p, c)
198	}
199}
200
201impl<'a, K: Parse<'a> + Peek<'a>> Parse<'a> for RelativeChannelValue<'a, K> {
202	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
203	where
204		I: Iterator<Item = Cursor> + Clone,
205	{
206		// Try keyword first (channel keywords are single idents like r, g, b)
207		if let Some(kw) = p.parse_if_peek::<K>()? {
208			Ok(Self::Keyword(kw))
209		} else {
210			Ok(Self::Value(p.parse::<NoneOr<NumericValue<NumberOrPercentage>>>()?))
211		}
212	}
213}
214
215/// A hue-position channel value in relative color syntax.
216/// Accepts angle, number, `none`, or a channel keyword.
217#[node]
218#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
219#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
220#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
221#[derive(csskit_derives::NodeWithMetadata)]
222pub enum RelativeHueValue<'a, K> {
223	Keyword(K),
224	Value(NoneOr<NumericValue<'a, AngleOrNumber>>),
225}
226
227impl<'a, K: Peek<'a> + Parse<'a>> Peek<'a> for RelativeHueValue<'a, K> {
228	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
229	where
230		I: Iterator<Item = Cursor> + Clone,
231	{
232		K::peek(p, c) || NoneOr::<NumericValue<AngleOrNumber>>::peek(p, c)
233	}
234}
235
236impl<'a, K: Parse<'a> + Peek<'a>> Parse<'a> for RelativeHueValue<'a, K> {
237	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
238	where
239		I: Iterator<Item = Cursor> + Clone,
240	{
241		if let Some(kw) = p.parse_if_peek::<K>()? {
242			Ok(Self::Keyword(kw))
243		} else {
244			Ok(Self::Value(p.parse::<NoneOr<NumericValue<AngleOrNumber>>>()?))
245		}
246	}
247}
248
249/// Relative color params for `rgb()` / `rgba()`.
250///
251/// ```text,ignore
252/// from <color> <channel> <channel> <channel> [ / <alpha-channel> ]?
253/// ```
254///
255/// Where each channel can be a number, percentage, `none`, or a channel keyword (r, g, b, alpha).
256#[node]
257#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
258#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
259#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
260#[derive(csskit_derives::NodeWithMetadata)]
261pub struct RgbRelativeParams<'a> {
262	pub origin: RelativeColorOrigin<'a>,
263	pub red: RelativeChannelValue<'a, RgbChannelKeyword>,
264	pub green: RelativeChannelValue<'a, RgbChannelKeyword>,
265	pub blue: RelativeChannelValue<'a, RgbChannelKeyword>,
266	#[semantic_eq(skip)]
267	pub slash: Option<T![/]>,
268	pub alpha: Option<RelativeChannelValue<'a, RgbChannelKeyword>>,
269}
270
271/// Relative color params for `hsl()` / `hsla()`.
272///
273/// ```text,ignore
274/// from <color> <hue-channel> <channel> <channel> [ / <alpha-channel> ]?
275/// ```
276#[node]
277#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
278#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
279#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
280#[derive(csskit_derives::NodeWithMetadata)]
281pub struct HslRelativeParams<'a> {
282	pub origin: RelativeColorOrigin<'a>,
283	pub hue: RelativeHueValue<'a, HslChannelKeyword>,
284	pub saturation: RelativeChannelValue<'a, HslChannelKeyword>,
285	pub lightness: RelativeChannelValue<'a, HslChannelKeyword>,
286	#[semantic_eq(skip)]
287	pub slash: Option<T![/]>,
288	pub alpha: Option<RelativeChannelValue<'a, HslChannelKeyword>>,
289}
290
291/// Relative color params for `hwb()`.
292///
293/// ```text,ignore
294/// from <color> <hue-channel> <channel> <channel> [ / <alpha-channel> ]?
295/// ```
296#[node]
297#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
298#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
299#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
300#[derive(csskit_derives::NodeWithMetadata)]
301pub struct HwbRelativeParams<'a> {
302	pub origin: RelativeColorOrigin<'a>,
303	pub hue: RelativeHueValue<'a, HwbChannelKeyword>,
304	pub whiteness: RelativeChannelValue<'a, HwbChannelKeyword>,
305	pub blackness: RelativeChannelValue<'a, HwbChannelKeyword>,
306	#[semantic_eq(skip)]
307	pub slash: Option<T![/]>,
308	pub alpha: Option<RelativeChannelValue<'a, HwbChannelKeyword>>,
309}
310
311/// Relative color params for `lab()` / `oklab()`.
312///
313/// ```text,ignore
314/// from <color> <channel> <channel> <channel> [ / <alpha-channel> ]?
315/// ```
316#[node]
317#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
318#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
319#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
320#[derive(csskit_derives::NodeWithMetadata)]
321pub struct LabRelativeParams<'a> {
322	pub origin: RelativeColorOrigin<'a>,
323	pub l: RelativeChannelValue<'a, LabChannelKeyword>,
324	pub a: RelativeChannelValue<'a, LabChannelKeyword>,
325	pub b: RelativeChannelValue<'a, LabChannelKeyword>,
326	#[semantic_eq(skip)]
327	pub slash: Option<T![/]>,
328	pub alpha: Option<RelativeChannelValue<'a, LabChannelKeyword>>,
329}
330
331/// Relative color params for `lch()` / `oklch()`.
332///
333/// ```text,ignore
334/// from <color> <channel> <channel> <hue-channel> [ / <alpha-channel> ]?
335/// ```
336#[node]
337#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
338#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
339#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
340#[derive(csskit_derives::NodeWithMetadata)]
341pub struct LchRelativeParams<'a> {
342	pub origin: RelativeColorOrigin<'a>,
343	pub lightness: RelativeChannelValue<'a, LchChannelKeyword>,
344	pub chroma: RelativeChannelValue<'a, LchChannelKeyword>,
345	pub hue: RelativeHueValue<'a, LchChannelKeyword>,
346	#[semantic_eq(skip)]
347	pub slash: Option<T![/]>,
348	pub alpha: Option<RelativeChannelValue<'a, LchChannelKeyword>>,
349}
350
351/// Relative color params for `color()`.
352///
353/// ```text,ignore
354/// from <color> <colorspace> <channel> <channel> <channel> [ / <alpha-channel> ]?
355/// ```
356///
357/// Channel keywords are the union of predefined-rgb (`r`, `g`, `b`) and xyz (`x`, `y`, `z`)
358/// plus `alpha`, since the valid set depends on the colorspace.
359#[node]
360#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
361#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
362#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
363#[derive(csskit_derives::NodeWithMetadata)]
364pub struct ColorRelativeParams<'a> {
365	pub origin: RelativeColorOrigin<'a>,
366	pub colorspace: super::color_function::ColorSpace,
367	pub c1: RelativeChannelValue<'a, ColorChannelKeyword>,
368	pub c2: RelativeChannelValue<'a, ColorChannelKeyword>,
369	pub c3: RelativeChannelValue<'a, ColorChannelKeyword>,
370	#[semantic_eq(skip)]
371	pub slash: Option<T![/]>,
372	pub alpha: Option<RelativeChannelValue<'a, ColorChannelKeyword>>,
373}
374
375/// Relative colour syntax for `rgb()`.
376///
377/// ```text,ignore
378/// rgb() = rgb( from <color> <channel> <channel> <channel> [ / <alpha-value> ]? )
379/// ```
380#[node]
381#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
382#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
383#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
384#[derive(csskit_derives::NodeWithMetadata)]
385pub struct RgbRelativeFunction<'a> {
386	#[atom(CssAtomSet::Rgb)]
387	pub name: T![Function],
388	pub params: RgbRelativeParams<'a>,
389	#[semantic_eq(skip)]
390	pub close: T![')'],
391}
392
393impl<'a> Peek<'a> for RgbRelativeFunction<'a> {
394	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
395	where
396		I: Iterator<Item = Cursor> + Clone,
397	{
398		<T![Function]>::peek(p, c)
399			&& p.equals_atom(c, &CssAtomSet::Rgb)
400			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
401	}
402}
403
404/// Relative colour syntax for `rgba()`.
405///
406/// ```text,ignore
407/// rgba() = rgba( from <color> <channel> <channel> <channel> [ / <alpha-value> ]? )
408/// ```
409#[node]
410#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
411#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
412#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
413#[derive(csskit_derives::NodeWithMetadata)]
414pub struct RgbaRelativeFunction<'a> {
415	#[atom(CssAtomSet::Rgba)]
416	pub name: T![Function],
417	pub params: RgbRelativeParams<'a>,
418	#[semantic_eq(skip)]
419	pub close: T![')'],
420}
421
422impl<'a> Peek<'a> for RgbaRelativeFunction<'a> {
423	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
424	where
425		I: Iterator<Item = Cursor> + Clone,
426	{
427		<T![Function]>::peek(p, c)
428			&& p.equals_atom(c, &CssAtomSet::Rgba)
429			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
430	}
431}
432
433/// Relative colour syntax for `hsl()`.
434///
435/// ```text,ignore
436/// hsl() = hsl( from <color> <hue-channel> <channel> <channel> [ / <alpha-value> ]? )
437/// ```
438#[node]
439#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
440#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
441#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
442#[derive(csskit_derives::NodeWithMetadata)]
443pub struct HslRelativeFunction<'a> {
444	#[atom(CssAtomSet::Hsl)]
445	pub name: T![Function],
446	pub params: HslRelativeParams<'a>,
447	#[semantic_eq(skip)]
448	pub close: T![')'],
449}
450
451impl<'a> Peek<'a> for HslRelativeFunction<'a> {
452	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
453	where
454		I: Iterator<Item = Cursor> + Clone,
455	{
456		<T![Function]>::peek(p, c)
457			&& p.equals_atom(c, &CssAtomSet::Hsl)
458			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
459	}
460}
461
462/// Relative colour syntax for `hsla()`.
463///
464/// ```text,ignore
465/// hsla() = hsla( from <color> <hue-channel> <channel> <channel> [ / <alpha-value> ]? )
466/// ```
467#[node]
468#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
469#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
470#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
471#[derive(csskit_derives::NodeWithMetadata)]
472pub struct HslaRelativeFunction<'a> {
473	#[atom(CssAtomSet::Hsla)]
474	pub name: T![Function],
475	pub params: HslRelativeParams<'a>,
476	#[semantic_eq(skip)]
477	pub close: T![')'],
478}
479
480impl<'a> Peek<'a> for HslaRelativeFunction<'a> {
481	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
482	where
483		I: Iterator<Item = Cursor> + Clone,
484	{
485		<T![Function]>::peek(p, c)
486			&& p.equals_atom(c, &CssAtomSet::Hsla)
487			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
488	}
489}
490
491/// Relative colour syntax for `hwb()`.
492///
493/// ```text,ignore
494/// hwb() = hwb( from <color> <hue-channel> <channel> <channel> [ / <alpha-value> ]? )
495/// ```
496#[node]
497#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
498#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
499#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
500#[derive(csskit_derives::NodeWithMetadata)]
501pub struct HwbRelativeFunction<'a> {
502	#[atom(CssAtomSet::Hwb)]
503	pub name: T![Function],
504	pub params: HwbRelativeParams<'a>,
505	#[semantic_eq(skip)]
506	pub close: T![')'],
507}
508
509impl<'a> Peek<'a> for HwbRelativeFunction<'a> {
510	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
511	where
512		I: Iterator<Item = Cursor> + Clone,
513	{
514		<T![Function]>::peek(p, c)
515			&& p.equals_atom(c, &CssAtomSet::Hwb)
516			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
517	}
518}
519
520/// Relative colour syntax for `lab()`.
521///
522/// ```text,ignore
523/// lab() = lab( from <color> <channel> <channel> <channel> [ / <alpha-value> ]? )
524/// ```
525#[node]
526#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
527#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
528#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
529#[derive(csskit_derives::NodeWithMetadata)]
530pub struct LabRelativeFunction<'a> {
531	#[atom(CssAtomSet::Lab)]
532	pub name: T![Function],
533	pub params: LabRelativeParams<'a>,
534	#[semantic_eq(skip)]
535	pub close: T![')'],
536}
537
538impl<'a> Peek<'a> for LabRelativeFunction<'a> {
539	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
540	where
541		I: Iterator<Item = Cursor> + Clone,
542	{
543		<T![Function]>::peek(p, c)
544			&& p.equals_atom(c, &CssAtomSet::Lab)
545			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
546	}
547}
548
549/// Relative colour syntax for `lch()`.
550///
551/// ```text,ignore
552/// lch() = lch( from <color> <channel> <channel> <hue-channel> [ / <alpha-value> ]? )
553/// ```
554#[node]
555#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
556#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
557#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
558#[derive(csskit_derives::NodeWithMetadata)]
559pub struct LchRelativeFunction<'a> {
560	#[atom(CssAtomSet::Lch)]
561	pub name: T![Function],
562	pub params: LchRelativeParams<'a>,
563	#[semantic_eq(skip)]
564	pub close: T![')'],
565}
566
567impl<'a> Peek<'a> for LchRelativeFunction<'a> {
568	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
569	where
570		I: Iterator<Item = Cursor> + Clone,
571	{
572		<T![Function]>::peek(p, c)
573			&& p.equals_atom(c, &CssAtomSet::Lch)
574			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
575	}
576}
577
578/// Relative colour syntax for `oklab()`.
579///
580/// ```text,ignore
581/// oklab() = oklab( from <color> <channel> <channel> <channel> [ / <alpha-value> ]? )
582/// ```
583#[node]
584#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
585#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
586#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
587#[derive(csskit_derives::NodeWithMetadata)]
588pub struct OklabRelativeFunction<'a> {
589	#[atom(CssAtomSet::Oklab)]
590	pub name: T![Function],
591	pub params: LabRelativeParams<'a>,
592	#[semantic_eq(skip)]
593	pub close: T![')'],
594}
595
596impl<'a> Peek<'a> for OklabRelativeFunction<'a> {
597	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
598	where
599		I: Iterator<Item = Cursor> + Clone,
600	{
601		<T![Function]>::peek(p, c)
602			&& p.equals_atom(c, &CssAtomSet::Oklab)
603			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
604	}
605}
606
607/// Relative colour syntax for `oklch()`.
608///
609/// ```text,ignore
610/// oklch() = oklch( from <color> <channel> <channel> <hue-channel> [ / <alpha-value> ]? )
611/// ```
612#[node]
613#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
614#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
615#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
616#[derive(csskit_derives::NodeWithMetadata)]
617pub struct OklchRelativeFunction<'a> {
618	#[atom(CssAtomSet::Oklch)]
619	pub name: T![Function],
620	pub params: LchRelativeParams<'a>,
621	#[semantic_eq(skip)]
622	pub close: T![')'],
623}
624
625impl<'a> Peek<'a> for OklchRelativeFunction<'a> {
626	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
627	where
628		I: Iterator<Item = Cursor> + Clone,
629	{
630		<T![Function]>::peek(p, c)
631			&& p.equals_atom(c, &CssAtomSet::Oklch)
632			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
633	}
634}
635
636/// Relative colour syntax for `color()`.
637///
638/// ```text,ignore
639/// color() = color( from <color> <colorspace> <channel> <channel> <channel> [ / <alpha-value> ]? )
640/// ```
641#[node]
642#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
643#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
644#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
645#[derive(csskit_derives::NodeWithMetadata)]
646pub struct ColorRelativeFunction<'a> {
647	#[atom(CssAtomSet::Color)]
648	pub name: T![Function],
649	pub params: ColorRelativeParams<'a>,
650	#[semantic_eq(skip)]
651	pub close: T![')'],
652}
653
654impl<'a> Peek<'a> for ColorRelativeFunction<'a> {
655	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
656	where
657		I: Iterator<Item = Cursor> + Clone,
658	{
659		<T![Function]>::peek(p, c)
660			&& p.equals_atom(c, &CssAtomSet::Color)
661			&& p.equals_atom(p.peek_n(2), &CssAtomSet::From)
662	}
663}
664
665/// All colour functions in relative mode (`from <color>` origin).
666///
667/// Mirrors the structure of `ColorFunction` but only matches when `from` is present.
668/// Relative variants are checked before their absolute counterparts in `ColorFunction`.
669#[node]
670#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
671#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
672#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(all))]
673#[derive(csskit_derives::NodeWithMetadata)]
674pub enum RelativeColorFunction<'a> {
675	Color(Box<'a, ColorRelativeFunction<'a>>),
676	Rgb(Box<'a, RgbRelativeFunction<'a>>),
677	Rgba(Box<'a, RgbaRelativeFunction<'a>>),
678	Hsl(Box<'a, HslRelativeFunction<'a>>),
679	Hsla(Box<'a, HslaRelativeFunction<'a>>),
680	Hwb(Box<'a, HwbRelativeFunction<'a>>),
681	Lab(Box<'a, LabRelativeFunction<'a>>),
682	Lch(Box<'a, LchRelativeFunction<'a>>),
683	Oklab(Box<'a, OklabRelativeFunction<'a>>),
684	Oklch(Box<'a, OklchRelativeFunction<'a>>),
685}
686
687#[cfg(feature = "chromashift")]
688mod chromashift_impl {
689	use super::super::color_function::ColorSpace;
690	use super::*;
691	use crate::ToChromashift;
692	use chromashift::{
693		A98Rgb, Color, DisplayP3, Hsl, Hwb, Lab, Lch, LinearRgb, Oklab, Oklch, ProphotoRgb, Rec2020, Srgb, ToAlpha,
694		XyzD50, XyzD65,
695	};
696
697	trait ChannelMap<K> {
698		fn channel(&self, kw: &K) -> f32;
699	}
700
701	impl ChannelMap<RgbChannelKeyword> for Srgb {
702		fn channel(&self, kw: &RgbChannelKeyword) -> f32 {
703			match kw {
704				RgbChannelKeyword::R(_) => self.red as f32,
705				RgbChannelKeyword::G(_) => self.green as f32,
706				RgbChannelKeyword::B(_) => self.blue as f32,
707				RgbChannelKeyword::Alpha(_) => self.alpha,
708			}
709		}
710	}
711
712	impl ChannelMap<HslChannelKeyword> for Hsl {
713		fn channel(&self, kw: &HslChannelKeyword) -> f32 {
714			match kw {
715				HslChannelKeyword::H(_) => self.hue,
716				HslChannelKeyword::S(_) => self.saturation,
717				HslChannelKeyword::L(_) => self.lightness,
718				HslChannelKeyword::Alpha(_) => self.alpha,
719			}
720		}
721	}
722
723	impl ChannelMap<HwbChannelKeyword> for Hwb {
724		fn channel(&self, kw: &HwbChannelKeyword) -> f32 {
725			match kw {
726				HwbChannelKeyword::H(_) => self.hue,
727				HwbChannelKeyword::W(_) => self.whiteness,
728				HwbChannelKeyword::B(_) => self.blackness,
729				HwbChannelKeyword::Alpha(_) => self.alpha,
730			}
731		}
732	}
733
734	impl ChannelMap<LabChannelKeyword> for Lab {
735		fn channel(&self, kw: &LabChannelKeyword) -> f32 {
736			match kw {
737				LabChannelKeyword::L(_) => self.lightness as f32,
738				LabChannelKeyword::A(_) => self.a as f32,
739				LabChannelKeyword::B(_) => self.b as f32,
740				LabChannelKeyword::Alpha(_) => self.alpha,
741			}
742		}
743	}
744
745	impl ChannelMap<LabChannelKeyword> for Oklab {
746		fn channel(&self, kw: &LabChannelKeyword) -> f32 {
747			match kw {
748				LabChannelKeyword::L(_) => self.lightness as f32,
749				LabChannelKeyword::A(_) => self.a as f32,
750				LabChannelKeyword::B(_) => self.b as f32,
751				LabChannelKeyword::Alpha(_) => self.alpha,
752			}
753		}
754	}
755
756	impl ChannelMap<LchChannelKeyword> for Lch {
757		fn channel(&self, kw: &LchChannelKeyword) -> f32 {
758			match kw {
759				LchChannelKeyword::L(_) => self.lightness as f32,
760				LchChannelKeyword::C(_) => self.chroma as f32,
761				LchChannelKeyword::H(_) => self.hue as f32,
762				LchChannelKeyword::Alpha(_) => self.alpha,
763			}
764		}
765	}
766
767	impl ChannelMap<LchChannelKeyword> for Oklch {
768		fn channel(&self, kw: &LchChannelKeyword) -> f32 {
769			match kw {
770				LchChannelKeyword::L(_) => self.lightness as f32,
771				LchChannelKeyword::C(_) => self.chroma as f32,
772				LchChannelKeyword::H(_) => self.hue as f32,
773				LchChannelKeyword::Alpha(_) => self.alpha,
774			}
775		}
776	}
777
778	fn resolve_channel<K, O: ChannelMap<K>>(
779		v: &RelativeChannelValue<'_, K>,
780		origin: &O,
781		pct_scale: f32,
782	) -> Option<f32> {
783		match v {
784			RelativeChannelValue::Keyword(kw) => Some(origin.channel(kw)),
785			RelativeChannelValue::Value(NoneOr::None(_)) => None,
786			RelativeChannelValue::Value(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n)))) => {
787				Some(n.value())
788			}
789			RelativeChannelValue::Value(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p)))) => {
790				Some(p.value() / 100.0 * pct_scale)
791			}
792			RelativeChannelValue::Value(NoneOr::Some(_)) => None,
793		}
794	}
795
796	fn resolve_alpha<K, O: ChannelMap<K>>(v: &RelativeChannelValue<'_, K>, origin: &O) -> Option<f32> {
797		match v {
798			RelativeChannelValue::Keyword(kw) => Some(origin.channel(kw)),
799			RelativeChannelValue::Value(NoneOr::None(_)) => None,
800			RelativeChannelValue::Value(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n)))) => {
801				Some(n.value() * 100.0)
802			}
803			RelativeChannelValue::Value(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p)))) => {
804				Some(p.value())
805			}
806			RelativeChannelValue::Value(NoneOr::Some(_)) => None,
807		}
808	}
809
810	fn resolve_hue_channel<K, O: ChannelMap<K>>(v: &RelativeHueValue<'_, K>, origin: &O) -> Option<f32> {
811		match v {
812			RelativeHueValue::Keyword(kw) => Some(origin.channel(kw)),
813			RelativeHueValue::Value(NoneOr::None(_)) => None,
814			RelativeHueValue::Value(NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(n)))) => Some(n.value()),
815			RelativeHueValue::Value(NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(a)))) => {
816				Some(a.as_degrees())
817			}
818			RelativeHueValue::Value(NoneOr::Some(_)) => None,
819		}
820	}
821
822	impl crate::ToChromashift for RelativeColorFunction<'_> {
823		fn to_chromashift(&self) -> Option<Color> {
824			match self {
825				Self::Rgb(f) => f.to_chromashift(),
826				Self::Rgba(f) => f.to_chromashift(),
827				Self::Hsl(f) => f.to_chromashift(),
828				Self::Hsla(f) => f.to_chromashift(),
829				Self::Hwb(f) => f.to_chromashift(),
830				Self::Lab(f) => f.to_chromashift(),
831				Self::Lch(f) => f.to_chromashift(),
832				Self::Oklab(f) => f.to_chromashift(),
833				Self::Oklch(f) => f.to_chromashift(),
834				Self::Color(f) => f.to_chromashift(),
835			}
836		}
837	}
838
839	fn rgb_relative_params_to_chromashift(params: &RgbRelativeParams<'_>) -> Option<Color> {
840		let origin = params.origin.color.to_chromashift()?;
841		let srgb = Srgb::from(origin);
842		let red = resolve_channel(&params.red, &srgb, 255.0)?.round() as u8;
843		let green = resolve_channel(&params.green, &srgb, 255.0)?.round() as u8;
844		let blue = resolve_channel(&params.blue, &srgb, 255.0)?.round() as u8;
845		let alpha = params.alpha.as_ref().map_or(Some(100.0), |a| resolve_alpha(a, &srgb))?;
846		Some(Color::Srgb(Srgb::new(red, green, blue, alpha)))
847	}
848
849	impl ToChromashift for RgbRelativeFunction<'_> {
850		fn to_chromashift(&self) -> Option<Color> {
851			rgb_relative_params_to_chromashift(&self.params)
852		}
853	}
854
855	impl ToChromashift for RgbaRelativeFunction<'_> {
856		fn to_chromashift(&self) -> Option<Color> {
857			rgb_relative_params_to_chromashift(&self.params)
858		}
859	}
860
861	fn hsl_relative_params_to_chromashift(params: &HslRelativeParams<'_>) -> Option<Color> {
862		let origin = params.origin.color.to_chromashift()?;
863		let hsl = Hsl::from(origin);
864		let hue = resolve_hue_channel(&params.hue, &hsl)?;
865		let saturation = resolve_channel(&params.saturation, &hsl, 100.0)?;
866		let lightness = resolve_channel(&params.lightness, &hsl, 100.0)?;
867		let alpha = params.alpha.as_ref().map_or(Some(100.0), |a| resolve_alpha(a, &hsl))?;
868		Some(Color::Hsl(Hsl::new(hue, saturation, lightness, alpha)))
869	}
870
871	impl ToChromashift for HslRelativeFunction<'_> {
872		fn to_chromashift(&self) -> Option<Color> {
873			hsl_relative_params_to_chromashift(&self.params)
874		}
875	}
876
877	impl ToChromashift for HslaRelativeFunction<'_> {
878		fn to_chromashift(&self) -> Option<Color> {
879			hsl_relative_params_to_chromashift(&self.params)
880		}
881	}
882
883	impl ToChromashift for HwbRelativeFunction<'_> {
884		fn to_chromashift(&self) -> Option<Color> {
885			let origin = self.params.origin.color.to_chromashift()?;
886			let hwb = Hwb::from(origin);
887			let hue = resolve_hue_channel(&self.params.hue, &hwb)?;
888			let whiteness = resolve_channel(&self.params.whiteness, &hwb, 100.0)?;
889			let blackness = resolve_channel(&self.params.blackness, &hwb, 100.0)?;
890			let alpha = self.params.alpha.as_ref().map_or(Some(100.0), |a| resolve_alpha(a, &hwb))?;
891			Some(Color::Hwb(Hwb::new(hue, whiteness, blackness, alpha)))
892		}
893	}
894
895	impl ToChromashift for LabRelativeFunction<'_> {
896		fn to_chromashift(&self) -> Option<Color> {
897			let origin = self.params.origin.color.to_chromashift()?;
898			let lab = Lab::from(origin);
899			let l = resolve_channel(&self.params.l, &lab, 100.0)? as f64;
900			let a = resolve_channel(&self.params.a, &lab, 125.0)? as f64;
901			let b = resolve_channel(&self.params.b, &lab, 125.0)? as f64;
902			let alpha = self.params.alpha.as_ref().map_or(Some(lab.alpha), |ch| resolve_alpha(ch, &lab))?;
903			Some(Color::Lab(Lab::new(l, a, b, alpha)))
904		}
905	}
906
907	impl ToChromashift for LchRelativeFunction<'_> {
908		fn to_chromashift(&self) -> Option<Color> {
909			let origin = self.params.origin.color.to_chromashift()?;
910			let lch = Lch::from(origin);
911			let lightness = resolve_channel(&self.params.lightness, &lch, 100.0)? as f64;
912			let chroma = resolve_channel(&self.params.chroma, &lch, 150.0)? as f64;
913			let hue = resolve_hue_channel(&self.params.hue, &lch)? as f64;
914			let alpha = self.params.alpha.as_ref().map_or(Some(lch.alpha), |ch| resolve_alpha(ch, &lch))?;
915			Some(Color::Lch(Lch::new(lightness, chroma, hue, alpha)))
916		}
917	}
918
919	impl ToChromashift for OklabRelativeFunction<'_> {
920		fn to_chromashift(&self) -> Option<Color> {
921			let origin = self.params.origin.color.to_chromashift()?;
922			let oklab = Oklab::from(origin);
923			let l = resolve_channel(&self.params.l, &oklab, 1.0)? as f64;
924			let a = resolve_channel(&self.params.a, &oklab, 0.4)? as f64;
925			let b = resolve_channel(&self.params.b, &oklab, 0.4)? as f64;
926			let alpha = self.params.alpha.as_ref().map_or(Some(oklab.alpha), |ch| resolve_alpha(ch, &oklab))?;
927			Some(Color::Oklab(Oklab::new(l, a, b, alpha)))
928		}
929	}
930
931	impl ToChromashift for OklchRelativeFunction<'_> {
932		fn to_chromashift(&self) -> Option<Color> {
933			let origin = self.params.origin.color.to_chromashift()?;
934			let oklch = Oklch::from(origin);
935			let lightness = resolve_channel(&self.params.lightness, &oklch, 1.0)? as f64;
936			let chroma = resolve_channel(&self.params.chroma, &oklch, 0.4)? as f64;
937			let hue = resolve_hue_channel(&self.params.hue, &oklch)? as f64;
938			let alpha = self.params.alpha.as_ref().map_or(Some(oklch.alpha), |ch| resolve_alpha(ch, &oklch))?;
939			Some(Color::Oklch(Oklch::new(lightness, chroma, hue, alpha)))
940		}
941	}
942
943	impl ToChromashift for ColorRelativeFunction<'_> {
944		fn to_chromashift(&self) -> Option<Color> {
945			let origin = self.params.origin.color.to_chromashift()?;
946			let space = &self.params.colorspace;
947
948			let unit_from_kw = |kw: &ColorChannelKeyword| -> f64 {
949				match kw {
950					ColorChannelKeyword::R(_) => Srgb::from(origin).red as f64 / 255.0,
951					ColorChannelKeyword::G(_) => Srgb::from(origin).green as f64 / 255.0,
952					ColorChannelKeyword::B(_) => Srgb::from(origin).blue as f64 / 255.0,
953					ColorChannelKeyword::X(_) => XyzD65::from(origin).x / 100.0,
954					ColorChannelKeyword::Y(_) => XyzD65::from(origin).y / 100.0,
955					ColorChannelKeyword::Z(_) => XyzD65::from(origin).z / 100.0,
956					ColorChannelKeyword::Alpha(_) => origin.to_alpha() as f64 / 100.0,
957				}
958			};
959
960			let resolve_c = |v: &RelativeChannelValue<'_, ColorChannelKeyword>| -> Option<f64> {
961				match v {
962					RelativeChannelValue::Keyword(kw) => Some(unit_from_kw(kw)),
963					RelativeChannelValue::Value(NoneOr::None(_)) => None,
964					RelativeChannelValue::Value(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n)))) => {
965						Some(n.value() as f64)
966					}
967					RelativeChannelValue::Value(NoneOr::Some(NumericValue::Literal(
968						NumberOrPercentage::Percentage(p),
969					))) => Some(p.value() as f64 / 100.0),
970					RelativeChannelValue::Value(NoneOr::Some(_)) => None,
971				}
972			};
973
974			let resolve_a = |v: &RelativeChannelValue<'_, ColorChannelKeyword>| -> Option<f32> {
975				match v {
976					RelativeChannelValue::Keyword(kw) => Some((unit_from_kw(kw) * 100.0) as f32),
977					RelativeChannelValue::Value(NoneOr::None(_)) => None,
978					RelativeChannelValue::Value(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n)))) => {
979						Some(n.value() * 100.0)
980					}
981					RelativeChannelValue::Value(NoneOr::Some(NumericValue::Literal(
982						NumberOrPercentage::Percentage(p),
983					))) => Some(p.value()),
984					RelativeChannelValue::Value(NoneOr::Some(_)) => None,
985				}
986			};
987
988			let c1 = resolve_c(&self.params.c1)?;
989			let c2 = resolve_c(&self.params.c2)?;
990			let c3 = resolve_c(&self.params.c3)?;
991			let alpha = self.params.alpha.as_ref().map_or(Some(100.0), resolve_a)?;
992
993			match space {
994				ColorSpace::Srgb(_) => Some(Color::Srgb(Srgb::new(
995					(c1 * 255.0).round() as u8,
996					(c2 * 255.0).round() as u8,
997					(c3 * 255.0).round() as u8,
998					alpha,
999				))),
1000				ColorSpace::SrgbLinear(_) => Some(Color::LinearRgb(LinearRgb::new(c1, c2, c3, alpha))),
1001				ColorSpace::DisplayP3(_) => Some(Color::DisplayP3(DisplayP3::new(c1, c2, c3, alpha))),
1002				ColorSpace::A98Rgb(_) => Some(Color::A98Rgb(A98Rgb::new(c1, c2, c3, alpha))),
1003				ColorSpace::ProphotoRgb(_) => Some(Color::ProphotoRgb(ProphotoRgb::new(c1, c2, c3, alpha))),
1004				ColorSpace::Rec2020(_) => Some(Color::Rec2020(Rec2020::new(c1, c2, c3, alpha))),
1005				ColorSpace::Xyz(_) | ColorSpace::XyzD65(_) => {
1006					Some(Color::XyzD65(XyzD65::new(c1 * 100.0, c2 * 100.0, c3 * 100.0, alpha)))
1007				}
1008				ColorSpace::XyzD50(_) => Some(Color::XyzD50(XyzD50::new(c1 * 100.0, c2 * 100.0, c3 * 100.0, alpha))),
1009			}
1010		}
1011	}
1012}
1013
1014#[cfg(test)]
1015mod tests {
1016	use super::*;
1017	use crate::CssAtomSet;
1018	use css_parse::{assert_parse, assert_peek_false};
1019
1020	#[test]
1021	fn test_writes() {
1022		assert_parse!(CssAtomSet::ATOMS, RgbChannelKeyword, "r");
1023		assert_parse!(CssAtomSet::ATOMS, RgbChannelKeyword, "alpha");
1024		assert_parse!(CssAtomSet::ATOMS, RelativeColorOrigin, "from red");
1025	}
1026
1027	#[test]
1028	fn test_errors() {
1029		assert_peek_false!(CssAtomSet::ATOMS, RgbChannelKeyword, "h");
1030		assert_peek_false!(CssAtomSet::ATOMS, HslChannelKeyword, "r");
1031	}
1032
1033	#[test]
1034	fn substitution_in_channels() {
1035		// Math/substitution in relative-colour channels alongside channel keywords.
1036		assert_parse!(CssAtomSet::ATOMS, RgbRelativeFunction, "rgb(from red calc(255/2) g var(--b))");
1037		assert_parse!(CssAtomSet::ATOMS, HslRelativeFunction, "hsl(from red calc(120deg) s calc(50%))");
1038	}
1039}