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