1use super::prelude::*;
2use crate::{AngleOrNumber, NoneOr, NumberOrPercentage, NumericValue};
3use css_parse::Box;
4
5#[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#[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#[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#[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#[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#[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#[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#[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#[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 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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(¶ms.red, &srgb, 255.0)?.round() as u8;
843 let green = resolve_channel(¶ms.green, &srgb, 255.0)?.round() as u8;
844 let blue = resolve_channel(¶ms.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(¶ms.hue, &hsl)?;
865 let saturation = resolve_channel(¶ms.saturation, &hsl, 100.0)?;
866 let lightness = resolve_channel(¶ms.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 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}