Skip to main content

css_ast/functions/
relative_color.rs

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