1use css_parse::Box;
2
3use super::prelude::*;
4use crate::{AngleOrZero, AnglePercentage, AtPosition, CalcableValue, ColorInterpolationMethod, LengthPercentage};
5
6#[node]
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)]
15#[derive(csskit_derives::NodeWithMetadata)]
16pub enum Gradient<'a> {
17 #[atom(CssAtomSet::LinearGradient)]
18 LinearGradientFunction(Box<'a, LinearGradientFunction<'a>>),
19 #[atom(CssAtomSet::RepeatingLinearGradient)]
20 RepeatingLinearGradientFunction(Box<'a, RepeatingLinearGradientFunction<'a>>),
21 #[atom(CssAtomSet::RadialGradient)]
22 RadialGradientFunction(Box<'a, RadialGradientFunction<'a>>),
23 #[atom(CssAtomSet::RepeatingRadialGradient)]
24 RepeatingRadialGradientFunction(Box<'a, RepeatingRadialGradientFunction<'a>>),
25 #[atom(CssAtomSet::ConicGradient)]
26 ConicGradientFunction(Box<'a, ConicGradientFunction<'a>>),
27 #[atom(CssAtomSet::RepeatingConicGradient)]
28 RepeatingConicGradientFunction(Box<'a, RepeatingConicGradientFunction<'a>>),
29}
30
31#[node]
38#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
40#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
41#[derive(csskit_derives::NodeWithMetadata)]
42#[metadata(value_kinds = Image)]
43pub struct LinearGradientFunction<'a> {
44 #[atom(CssAtomSet::LinearGradient)]
45 pub name: T![Function],
46 pub params: LinearGradientFunctionParams<'a>,
47 #[semantic_eq(skip)]
48 pub close: T![')'],
49}
50
51#[node]
52#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
54#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
55#[derive(csskit_derives::NodeWithMetadata)]
56pub struct LinearGradientFunctionParams<'a> {
57 pub options: Option<LinearGradientOptions<'a>>,
58 #[semantic_eq(skip)]
59 pub comma: Option<T![,]>,
60 pub stops: CommaSeparated<'a, ColorStopOrHint<'a>>,
61}
62
63#[node]
70#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
72#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
73#[derive(csskit_derives::NodeWithMetadata)]
74#[metadata(value_kinds = Image)]
75pub struct RepeatingLinearGradientFunction<'a> {
76 #[atom(CssAtomSet::RepeatingLinearGradient)]
77 pub name: T![Function],
78 pub params: RepeatingLinearGradientFunctionParams<'a>,
79 #[semantic_eq(skip)]
80 pub close: T![')'],
81}
82
83#[node]
84#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
86#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
87#[derive(csskit_derives::NodeWithMetadata)]
88pub struct RepeatingLinearGradientFunctionParams<'a> {
89 pub options: Option<LinearGradientOptions<'a>>,
90 #[semantic_eq(skip)]
91 pub comma: Option<T![,]>,
92 pub stops: CommaSeparated<'a, ColorStopOrHint<'a>>,
93}
94
95#[node]
101#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
103#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
104#[derive(csskit_derives::NodeWithMetadata)]
105#[parse(one_must_occur)]
106pub struct LinearGradientOptions<'a> {
107 pub direction: Option<LinearDirection<'a>>,
108 pub interpolation: Option<ColorInterpolationMethod>,
109}
110
111#[node]
117#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
119#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
120#[derive(csskit_derives::NodeWithMetadata)]
121#[metadata(value_kinds = Image)]
122pub struct RadialGradientFunction<'a> {
123 #[atom(CssAtomSet::RadialGradient)]
124 pub name: T![Function],
125 pub params: RadialGradientFunctionParams<'a>,
126 #[semantic_eq(skip)]
127 pub close: T![')'],
128}
129
130#[node]
131#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
133#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
134#[derive(csskit_derives::NodeWithMetadata)]
135pub struct RadialGradientFunctionParams<'a> {
136 pub options: Option<RadialGradientOptions<'a>>,
137 #[semantic_eq(skip)]
138 pub comma: Option<T![,]>,
139 pub stops: CommaSeparated<'a, ColorStopOrHint<'a>>,
140}
141
142#[node]
148#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
150#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
151#[derive(csskit_derives::NodeWithMetadata)]
152#[metadata(value_kinds = Image)]
153pub struct RepeatingRadialGradientFunction<'a> {
154 #[atom(CssAtomSet::RepeatingRadialGradient)]
155 pub name: T![Function],
156 pub params: RepeatingRadialGradientFunctionParams<'a>,
157 #[semantic_eq(skip)]
158 pub close: T![')'],
159}
160
161#[node]
162#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
163#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
164#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
165#[derive(csskit_derives::NodeWithMetadata)]
166pub struct RepeatingRadialGradientFunctionParams<'a> {
167 pub options: Option<RadialGradientOptions<'a>>,
168 #[semantic_eq(skip)]
169 pub comma: Option<T![,]>,
170 pub stops: CommaSeparated<'a, ColorStopOrHint<'a>>,
171}
172
173#[node]
179#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
181#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
182#[derive(csskit_derives::NodeWithMetadata)]
183#[parse(one_must_occur)]
184pub struct RadialGradientOptions<'a> {
185 pub geometry: Option<RadialGeometry<'a>>,
186 pub interpolation: Option<ColorInterpolationMethod>,
187}
188
189#[node]
193#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
195#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
196#[derive(csskit_derives::NodeWithMetadata)]
197pub struct RadialGeometry<'a> {
198 pub ending_shape: Option<RadialEndingShape<'a>>,
199 pub at: Option<AtPosition<'a>>,
200}
201
202#[node]
206#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
207#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
208#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
209#[derive(csskit_derives::NodeWithMetadata)]
210#[parse(one_must_occur)]
211pub struct RadialEndingShape<'a> {
212 pub shape: Option<RadialShape>,
213 pub size: Option<RadialSize<'a>>,
214}
215
216#[node]
222#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
223#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
224#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
225#[derive(csskit_derives::NodeWithMetadata)]
226#[metadata(value_kinds = Image)]
227pub struct ConicGradientFunction<'a> {
228 #[atom(CssAtomSet::ConicGradient)]
229 pub name: T![Function],
230 pub params: ConicGradientFunctionParams<'a>,
231 #[semantic_eq(skip)]
232 pub close: T![')'],
233}
234
235#[node]
236#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
238#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
239#[derive(csskit_derives::NodeWithMetadata)]
240pub struct ConicGradientFunctionParams<'a> {
241 pub options: Option<ConicGradientOptions<'a>>,
242 #[semantic_eq(skip)]
243 pub comma: Option<T![,]>,
244 pub stops: CommaSeparated<'a, AngularColorStopOrHint<'a>>,
245}
246
247#[node]
253#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
255#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
256#[derive(csskit_derives::NodeWithMetadata)]
257#[metadata(value_kinds = Image)]
258pub struct RepeatingConicGradientFunction<'a> {
259 #[atom(CssAtomSet::RepeatingConicGradient)]
260 pub name: T![Function],
261 pub params: RepeatingConicGradientFunctionParams<'a>,
262 #[semantic_eq(skip)]
263 pub close: T![')'],
264}
265
266#[node]
267#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
268#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
269#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
270#[derive(csskit_derives::NodeWithMetadata)]
271pub struct RepeatingConicGradientFunctionParams<'a> {
272 pub options: Option<ConicGradientOptions<'a>>,
273 #[semantic_eq(skip)]
274 pub comma: Option<T![,]>,
275 pub stops: CommaSeparated<'a, AngularColorStopOrHint<'a>>,
276}
277
278#[node]
284#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
285#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
286#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
287#[derive(csskit_derives::NodeWithMetadata)]
288#[parse(one_must_occur)]
289pub struct ConicGradientOptions<'a> {
290 pub geometry: Option<ConicGeometry<'a>>,
291 pub interpolation: Option<ColorInterpolationMethod>,
292}
293
294#[node]
298#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
299#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
300#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
301#[derive(csskit_derives::NodeWithMetadata)]
302pub struct ConicGeometry<'a> {
303 pub from: Option<ConicFrom<'a>>,
304 pub at: Option<AtPosition<'a>>,
305}
306
307#[node]
311#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
312#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
313#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
314#[derive(csskit_derives::NodeWithMetadata)]
315pub struct ConicFrom<'a> {
316 #[atom(CssAtomSet::From)]
317 pub keyword: T![Ident],
318 pub angle: CalcableValue<'a, AngleOrZero>,
319}
320
321#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
322#[node]
323#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
324#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
325#[cfg_attr(feature = "visitable", visit(skip))]
326#[derive(csskit_derives::NodeWithMetadata)]
327pub enum NamedDirection {
328 #[atom(CssAtomSet::Bottom)]
329 Bottom(T![Ident]),
330 #[atom(CssAtomSet::Top)]
331 Top(T![Ident]),
332 #[atom(CssAtomSet::Left)]
333 Left(T![Ident]),
334 #[atom(CssAtomSet::Right)]
335 Right(T![Ident]),
336}
337
338#[node]
339#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
340#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
341#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
342#[derive(csskit_derives::NodeWithMetadata)]
343pub enum LinearDirection<'a> {
344 Angle(CalcableValue<'a, Angle>),
345 Named(#[atom(CssAtomSet::To)] T![Ident], NamedDirection, Option<NamedDirection>),
346}
347
348#[node]
356#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
357#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
358#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
359#[derive(csskit_derives::NodeWithMetadata)]
360pub enum RadialSize<'a> {
361 Extent(RadialExtent, Option<RadialExtent>),
362 Circular(CalcableValue<'a, LengthPercentage>),
363 Elliptical(CalcableValue<'a, LengthPercentage>, CalcableValue<'a, LengthPercentage>),
364}
365
366#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
372#[node]
373#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
374#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
375#[cfg_attr(feature = "visitable", visit(skip))]
376#[derive(csskit_derives::NodeWithMetadata)]
377pub enum RadialExtent {
378 #[atom(CssAtomSet::ClosestCorner)]
379 ClosestCorner(T![Ident]),
380 #[atom(CssAtomSet::ClosestSide)]
381 ClosestSide(T![Ident]),
382 #[atom(CssAtomSet::FarthestCorner)]
383 FarthestCorner(T![Ident]),
384 #[atom(CssAtomSet::FarthestSide)]
385 FarthestSide(T![Ident]),
386}
387
388impl<'a> Parse<'a> for RadialSize<'a> {
389 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
390 where
391 I: Iterator<Item = Cursor> + Clone,
392 {
393 if let Some(extent) = p.parse_if_peek::<RadialExtent>()? {
394 let second = p.parse_if_peek::<RadialExtent>()?;
395 return Ok(RadialSize::Extent(extent, second));
396 }
397 let first = p.parse::<CalcableValue<LengthPercentage>>()?;
398 if let Some(second) = p.parse_if_peek::<CalcableValue<LengthPercentage>>()? {
399 return Ok(Self::Elliptical(first, second));
400 }
401 Ok(Self::Circular(first))
402 }
403}
404
405#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
411#[node]
412#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
413#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
414#[cfg_attr(feature = "visitable", visit(skip))]
415#[derive(csskit_derives::NodeWithMetadata)]
416pub enum RadialShape {
417 #[atom(CssAtomSet::Circle)]
418 Circle(T![Ident]),
419 #[atom(CssAtomSet::Ellipse)]
420 Ellipse(T![Ident]),
421}
422
423#[node]
430#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
431#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
432#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
433#[derive(csskit_derives::NodeWithMetadata)]
434pub enum ColorStopOrHint<'a> {
435 Hint(CalcableValue<'a, LengthPercentage>),
436 Stop(Color<'a>, Option<CalcableValue<'a, LengthPercentage>>, Option<CalcableValue<'a, LengthPercentage>>),
437}
438
439#[node]
447#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
448#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
449#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
450#[derive(csskit_derives::NodeWithMetadata)]
451pub enum AngularColorStopOrHint<'a> {
452 Hint(CalcableValue<'a, AnglePercentage>),
453 Stop(Color<'a>, Option<CalcableValue<'a, AnglePercentage>>, Option<CalcableValue<'a, AnglePercentage>>),
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use crate::CssAtomSet;
460 use css_parse::{assert_parse, assert_parse_error};
461
462 #[test]
463 fn test_writes() {
464 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to bottom,yellow,blue)");
465 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(yellow,blue)");
466 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to bottom,#fff,#fff 85%,#e6e6e6)");
467 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(45deg,#808080 25%,transparent 25%)");
468 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to right,transparent,red 20%,red 80%,transparent)");
469 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(red 0% 40%,tan 0 70%,blue 0 100%)");
470 assert_parse!(
471 CssAtomSet::ATOMS,
472 Gradient,
473 "radial-gradient(closest-corner circle,rgba(1,65,255,0.4),rgba(1,65,255,0))"
474 );
475 }
476
477 #[test]
478 fn test_substitution() {
479 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(calc(45deg),red,blue)");
481 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(red calc(20% + 5px),blue)");
482 assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(calc(50px) circle,red,blue)");
483 }
484
485 #[test]
486 fn test_interpolation() {
487 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(in oklab,red,blue)");
488 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(45deg in oklch longer hue,red,blue)");
489 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(in srgb to right,red,blue)");
490 assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(in oklab circle,red,blue)");
491 assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(ellipse 10px 20px at 10% 20% in srgb,red,blue)");
492 assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(in hsl shorter hue,red,blue)");
493 }
494
495 #[test]
496 fn test_radial_geometry() {
497 assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(circle closest-side,red,blue)");
498 assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(at center,red,blue)");
499 assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(farthest-side at left top,red,blue)");
500 assert_parse!(CssAtomSet::ATOMS, Gradient, "repeating-radial-gradient(circle 10px at 0 0,red,blue 10%)");
501 }
502
503 #[test]
504 fn test_double_position_stops() {
505 assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(red 10% 20%,blue)");
506 assert_parse!(CssAtomSet::ATOMS, Gradient, "repeating-linear-gradient(red 0 10px,blue 10px 20px)");
507 }
508
509 #[test]
510 fn test_conic() {
511 assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(red,blue)");
512 assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(from 45deg,red,blue)");
513 assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(from 0,red,10%,blue)");
514 assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(at 10% 20%,red 0deg,blue 50% 75%,green)");
515 assert_parse!(CssAtomSet::ATOMS, Gradient, "conic-gradient(from 0.25turn at center in oklab,red,blue)");
516 assert_parse!(CssAtomSet::ATOMS, Gradient, "repeating-conic-gradient(red 0 10%,blue 10% 20%)");
517 }
518
519 #[test]
520 fn test_errors() {
521 assert_parse_error!(CssAtomSet::ATOMS, Gradient, "radial-gradient(at center circle,red,blue)");
522 assert_parse_error!(CssAtomSet::ATOMS, Gradient, "conic-gradient(at center from 45deg,red,blue)");
523 assert_parse_error!(CssAtomSet::ATOMS, Gradient, "linear-gradient(in oklab in srgb,red,blue)");
524 assert_parse_error!(CssAtomSet::ATOMS, Gradient, "conic-gradient(red 10px,blue)");
525 }
526}