1use super::angle::{Angle, AnglePercentage};
4use super::color::{ColorFallbackKind, CssColor};
5use super::length::{Length, LengthPercentage};
6use super::number::CSSNumber;
7use super::percentage::{DimensionPercentage, NumberOrPercentage, Percentage};
8use super::position::{HorizontalPositionKeyword, VerticalPositionKeyword};
9use super::position::{Position, PositionComponent};
10use crate::compat;
11use crate::error::{ParserError, PrinterError};
12use crate::macros::enum_property;
13use crate::prefixes::Feature;
14use crate::printer::Printer;
15use crate::targets::{should_compile, Browsers, Targets};
16use crate::traits::{IsCompatible, Parse, ToCss, TrySign, Zero};
17use crate::vendor_prefix::VendorPrefix;
18#[cfg(feature = "visitor")]
19use crate::visitor::Visit;
20use cssparser::*;
21
22#[cfg(feature = "serde")]
23use crate::serialization::ValueWrapper;
24
25#[derive(Debug, Clone, PartialEq)]
27#[cfg_attr(feature = "visitor", derive(Visit))]
28#[cfg_attr(
29 feature = "serde",
30 derive(serde::Serialize, serde::Deserialize),
31 serde(tag = "type", rename_all = "kebab-case")
32)]
33#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
34#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
35pub enum Gradient {
36 Linear(LinearGradient),
38 RepeatingLinear(LinearGradient),
40 Radial(RadialGradient),
42 RepeatingRadial(RadialGradient),
44 Conic(ConicGradient),
46 RepeatingConic(ConicGradient),
48 #[cfg_attr(feature = "serde", serde(rename = "webkit-gradient"))]
50 WebKitGradient(WebKitGradient),
51}
52
53impl Gradient {
54 pub fn get_vendor_prefix(&self) -> VendorPrefix {
56 match self {
57 Gradient::Linear(LinearGradient { vendor_prefix, .. })
58 | Gradient::RepeatingLinear(LinearGradient { vendor_prefix, .. })
59 | Gradient::Radial(RadialGradient { vendor_prefix, .. })
60 | Gradient::RepeatingRadial(RadialGradient { vendor_prefix, .. }) => *vendor_prefix,
61 Gradient::WebKitGradient(_) => VendorPrefix::WebKit,
62 _ => VendorPrefix::None,
63 }
64 }
65
66 pub fn get_necessary_prefixes(&self, targets: Targets) -> VendorPrefix {
68 macro_rules! get_prefixes {
69 ($feature: ident, $prefix: expr) => {
70 targets.prefixes($prefix, Feature::$feature)
71 };
72 }
73
74 match self {
75 Gradient::Linear(linear) => get_prefixes!(LinearGradient, linear.vendor_prefix),
76 Gradient::RepeatingLinear(linear) => get_prefixes!(RepeatingLinearGradient, linear.vendor_prefix),
77 Gradient::Radial(radial) => get_prefixes!(RadialGradient, radial.vendor_prefix),
78 Gradient::RepeatingRadial(radial) => get_prefixes!(RepeatingRadialGradient, radial.vendor_prefix),
79 _ => VendorPrefix::None,
80 }
81 }
82
83 pub fn get_prefixed(&self, prefix: VendorPrefix) -> Gradient {
85 match self {
86 Gradient::Linear(linear) => Gradient::Linear(LinearGradient {
87 vendor_prefix: prefix,
88 ..linear.clone()
89 }),
90 Gradient::RepeatingLinear(linear) => Gradient::RepeatingLinear(LinearGradient {
91 vendor_prefix: prefix,
92 ..linear.clone()
93 }),
94 Gradient::Radial(radial) => Gradient::Radial(RadialGradient {
95 vendor_prefix: prefix,
96 ..radial.clone()
97 }),
98 Gradient::RepeatingRadial(radial) => Gradient::RepeatingRadial(RadialGradient {
99 vendor_prefix: prefix,
100 ..radial.clone()
101 }),
102 _ => self.clone(),
103 }
104 }
105
106 pub fn get_legacy_webkit(&self) -> Result<Gradient, ()> {
110 Ok(Gradient::WebKitGradient(WebKitGradient::from_standard(self)?))
111 }
112
113 pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
115 match self {
116 Gradient::Linear(LinearGradient { items, .. })
117 | Gradient::Radial(RadialGradient { items, .. })
118 | Gradient::RepeatingLinear(LinearGradient { items, .. })
119 | Gradient::RepeatingRadial(RadialGradient { items, .. }) => {
120 let mut fallbacks = ColorFallbackKind::empty();
121 for item in items {
122 fallbacks |= item.get_necessary_fallbacks(targets)
123 }
124 fallbacks
125 }
126 Gradient::Conic(ConicGradient { items, .. }) | Gradient::RepeatingConic(ConicGradient { items, .. }) => {
127 let mut fallbacks = ColorFallbackKind::empty();
128 for item in items {
129 fallbacks |= item.get_necessary_fallbacks(targets)
130 }
131 fallbacks
132 }
133 Gradient::WebKitGradient(..) => ColorFallbackKind::empty(),
134 }
135 }
136
137 pub fn get_fallback(&self, kind: ColorFallbackKind) -> Gradient {
139 match self {
140 Gradient::Linear(g) => Gradient::Linear(g.get_fallback(kind)),
141 Gradient::RepeatingLinear(g) => Gradient::RepeatingLinear(g.get_fallback(kind)),
142 Gradient::Radial(g) => Gradient::Radial(g.get_fallback(kind)),
143 Gradient::RepeatingRadial(g) => Gradient::RepeatingRadial(g.get_fallback(kind)),
144 Gradient::Conic(g) => Gradient::Conic(g.get_fallback(kind)),
145 Gradient::RepeatingConic(g) => Gradient::RepeatingConic(g.get_fallback(kind)),
146 Gradient::WebKitGradient(g) => Gradient::WebKitGradient(g.get_fallback(kind)),
147 }
148 }
149}
150
151impl<'i> Parse<'i> for Gradient {
152 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
153 let location = input.current_source_location();
154 let func = input.expect_function()?.clone();
155 input.parse_nested_block(|input| {
156 match_ignore_ascii_case! { &func,
157 "linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::None)?)),
158 "repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::None)?)),
159 "radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::None)?)),
160 "repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::None)?)),
161 "conic-gradient" => Ok(Gradient::Conic(ConicGradient::parse(input)?)),
162 "repeating-conic-gradient" => Ok(Gradient::RepeatingConic(ConicGradient::parse(input)?)),
163 "-webkit-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::WebKit)?)),
164 "-webkit-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::WebKit)?)),
165 "-webkit-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::WebKit)?)),
166 "-webkit-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::WebKit)?)),
167 "-moz-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::Moz)?)),
168 "-moz-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::Moz)?)),
169 "-moz-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::Moz)?)),
170 "-moz-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::Moz)?)),
171 "-o-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::O)?)),
172 "-o-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::O)?)),
173 "-o-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::O)?)),
174 "-o-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::O)?)),
175 "-webkit-gradient" => Ok(Gradient::WebKitGradient(WebKitGradient::parse(input)?)),
176 _ => Err(location.new_unexpected_token_error(cssparser::Token::Ident(func.clone())))
177 }
178 })
179 }
180}
181
182impl ToCss for Gradient {
183 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
184 where
185 W: std::fmt::Write,
186 {
187 let (f, prefix) = match self {
188 Gradient::Linear(g) => ("linear-gradient(", Some(g.vendor_prefix)),
189 Gradient::RepeatingLinear(g) => ("repeating-linear-gradient(", Some(g.vendor_prefix)),
190 Gradient::Radial(g) => ("radial-gradient(", Some(g.vendor_prefix)),
191 Gradient::RepeatingRadial(g) => ("repeating-radial-gradient(", Some(g.vendor_prefix)),
192 Gradient::Conic(_) => ("conic-gradient(", None),
193 Gradient::RepeatingConic(_) => ("repeating-conic-gradient(", None),
194 Gradient::WebKitGradient(_) => ("-webkit-gradient(", None),
195 };
196
197 if let Some(prefix) = prefix {
198 prefix.to_css(dest)?;
199 }
200
201 dest.write_str(f)?;
202
203 match self {
204 Gradient::Linear(linear) | Gradient::RepeatingLinear(linear) => {
205 linear.to_css(dest, linear.vendor_prefix != VendorPrefix::None)?
206 }
207 Gradient::Radial(radial) | Gradient::RepeatingRadial(radial) => radial.to_css(dest)?,
208 Gradient::Conic(conic) | Gradient::RepeatingConic(conic) => conic.to_css(dest)?,
209 Gradient::WebKitGradient(g) => g.to_css(dest)?,
210 }
211
212 dest.write_char(')')
213 }
214}
215
216#[derive(Debug, Clone, PartialEq)]
218#[cfg_attr(feature = "visitor", derive(Visit))]
219#[cfg_attr(
220 feature = "serde",
221 derive(serde::Serialize, serde::Deserialize),
222 serde(rename_all = "camelCase")
223)]
224#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
225#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
226pub struct LinearGradient {
227 pub vendor_prefix: VendorPrefix,
229 pub direction: LineDirection,
231 pub items: Vec<GradientItem<LengthPercentage>>,
233}
234
235impl LinearGradient {
236 fn parse<'i, 't>(
237 input: &mut Parser<'i, 't>,
238 vendor_prefix: VendorPrefix,
239 ) -> Result<LinearGradient, ParseError<'i, ParserError<'i>>> {
240 let direction = if let Ok(direction) =
241 input.try_parse(|input| LineDirection::parse(input, vendor_prefix != VendorPrefix::None))
242 {
243 input.expect_comma()?;
244 direction
245 } else {
246 LineDirection::Vertical(VerticalPositionKeyword::Bottom)
247 };
248 let items = parse_items(input)?;
249 Ok(LinearGradient {
250 direction,
251 items,
252 vendor_prefix,
253 })
254 }
255
256 fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>
257 where
258 W: std::fmt::Write,
259 {
260 let angle = match &self.direction {
261 LineDirection::Vertical(VerticalPositionKeyword::Bottom) => 180.0,
262 LineDirection::Vertical(VerticalPositionKeyword::Top) => 0.0,
263 LineDirection::Angle(angle) => angle.to_degrees(),
264 _ => -1.0,
265 };
266
267 if angle == 180.0 {
269 serialize_items(&self.items, dest)
270
271 } else if angle == 0.0
274 && dest.minify
275 && self.items.iter().all(|item| {
276 matches!(
277 item,
278 GradientItem::Hint(LengthPercentage::Percentage(_))
279 | GradientItem::ColorStop(ColorStop {
280 position: None | Some(LengthPercentage::Percentage(_)),
281 ..
282 })
283 )
284 })
285 {
286 let items: Vec<GradientItem<LengthPercentage>> = self
287 .items
288 .iter()
289 .rev()
290 .map(|item| {
291 match item {
293 GradientItem::Hint(LengthPercentage::Percentage(p)) => {
294 GradientItem::Hint(LengthPercentage::Percentage(Percentage(1.0 - p.0)))
295 }
296 GradientItem::ColorStop(ColorStop { color, position }) => GradientItem::ColorStop(ColorStop {
297 color: color.clone(),
298 position: position.clone().map(|p| match p {
299 LengthPercentage::Percentage(p) => LengthPercentage::Percentage(Percentage(1.0 - p.0)),
300 _ => unreachable!(),
301 }),
302 }),
303 _ => unreachable!(),
304 }
305 })
306 .collect();
307 serialize_items(&items, dest)
308 } else {
309 if self.direction != LineDirection::Vertical(VerticalPositionKeyword::Bottom)
310 && self.direction != LineDirection::Angle(Angle::Deg(180.0))
311 {
312 self.direction.to_css(dest, is_prefixed)?;
313 dest.delim(',', false)?;
314 }
315
316 serialize_items(&self.items, dest)
317 }
318 }
319
320 fn get_fallback(&self, kind: ColorFallbackKind) -> LinearGradient {
321 LinearGradient {
322 direction: self.direction.clone(),
323 items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
324 vendor_prefix: self.vendor_prefix,
325 }
326 }
327}
328
329impl IsCompatible for LinearGradient {
330 fn is_compatible(&self, browsers: Browsers) -> bool {
331 self.items.iter().all(|item| item.is_compatible(browsers))
332 }
333}
334
335#[derive(Debug, Clone, PartialEq)]
337#[cfg_attr(feature = "visitor", derive(Visit))]
338#[cfg_attr(
339 feature = "serde",
340 derive(serde::Serialize, serde::Deserialize),
341 serde(rename_all = "camelCase")
342)]
343#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
344#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
345pub struct RadialGradient {
346 pub vendor_prefix: VendorPrefix,
348 pub shape: EndingShape,
350 pub position: Position,
352 pub items: Vec<GradientItem<LengthPercentage>>,
354}
355
356impl<'i> RadialGradient {
357 fn parse<'t>(
358 input: &mut Parser<'i, 't>,
359 vendor_prefix: VendorPrefix,
360 ) -> Result<RadialGradient, ParseError<'i, ParserError<'i>>> {
361 let shape = input.try_parse(EndingShape::parse).ok();
362 let position = input
363 .try_parse(|input| {
364 input.expect_ident_matching("at")?;
365 Position::parse(input)
366 })
367 .ok();
368
369 if shape.is_some() || position.is_some() {
370 input.expect_comma()?;
371 }
372
373 let items = parse_items(input)?;
374 Ok(RadialGradient {
375 shape: shape.unwrap_or_default(),
376 position: position.unwrap_or(Position::center()),
377 items,
378 vendor_prefix,
379 })
380 }
381}
382
383impl ToCss for RadialGradient {
384 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
385 where
386 W: std::fmt::Write,
387 {
388 if self.shape != EndingShape::default() {
389 self.shape.to_css(dest)?;
390 if self.position.is_center() {
391 dest.delim(',', false)?;
392 } else {
393 dest.write_char(' ')?;
394 }
395 }
396
397 if !self.position.is_center() {
398 dest.write_str("at ")?;
399 self.position.to_css(dest)?;
400 dest.delim(',', false)?;
401 }
402
403 serialize_items(&self.items, dest)
404 }
405}
406
407impl RadialGradient {
408 fn get_fallback(&self, kind: ColorFallbackKind) -> RadialGradient {
409 RadialGradient {
410 shape: self.shape.clone(),
411 position: self.position.clone(),
412 items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
413 vendor_prefix: self.vendor_prefix,
414 }
415 }
416}
417
418impl IsCompatible for RadialGradient {
419 fn is_compatible(&self, browsers: Browsers) -> bool {
420 self.items.iter().all(|item| item.is_compatible(browsers))
421 }
422}
423
424#[derive(Debug, Clone, PartialEq)]
428#[cfg_attr(feature = "visitor", derive(Visit))]
429#[cfg_attr(
430 feature = "serde",
431 derive(serde::Serialize, serde::Deserialize),
432 serde(tag = "type", rename_all = "kebab-case")
433)]
434#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
435pub enum LineDirection {
436 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<Angle>"))]
438 Angle(Angle),
439 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<HorizontalPositionKeyword>"))]
441 Horizontal(HorizontalPositionKeyword),
442 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<VerticalPositionKeyword>"))]
444 Vertical(VerticalPositionKeyword),
445 Corner {
447 horizontal: HorizontalPositionKeyword,
449 vertical: VerticalPositionKeyword,
451 },
452}
453
454impl LineDirection {
455 fn parse<'i, 't>(
456 input: &mut Parser<'i, 't>,
457 is_prefixed: bool,
458 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
459 if let Ok(angle) = input.try_parse(Angle::parse_with_unitless_zero) {
462 return Ok(LineDirection::Angle(angle));
463 }
464
465 if !is_prefixed {
466 input.expect_ident_matching("to")?;
467 }
468
469 if let Ok(x) = input.try_parse(HorizontalPositionKeyword::parse) {
470 if let Ok(y) = input.try_parse(VerticalPositionKeyword::parse) {
471 return Ok(LineDirection::Corner {
472 horizontal: x,
473 vertical: y,
474 });
475 }
476 return Ok(LineDirection::Horizontal(x));
477 }
478
479 let y = VerticalPositionKeyword::parse(input)?;
480 if let Ok(x) = input.try_parse(HorizontalPositionKeyword::parse) {
481 return Ok(LineDirection::Corner {
482 horizontal: x,
483 vertical: y,
484 });
485 }
486 Ok(LineDirection::Vertical(y))
487 }
488
489 fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>
490 where
491 W: std::fmt::Write,
492 {
493 match self {
494 LineDirection::Angle(angle) => angle.to_css(dest),
495 LineDirection::Horizontal(k) => {
496 if dest.minify {
497 match k {
498 HorizontalPositionKeyword::Left => dest.write_str("270deg"),
499 HorizontalPositionKeyword::Right => dest.write_str("90deg"),
500 }
501 } else {
502 if !is_prefixed {
503 dest.write_str("to ")?;
504 }
505 k.to_css(dest)
506 }
507 }
508 LineDirection::Vertical(k) => {
509 if dest.minify {
510 match k {
511 VerticalPositionKeyword::Top => dest.write_str("0deg"),
512 VerticalPositionKeyword::Bottom => dest.write_str("180deg"),
513 }
514 } else {
515 if !is_prefixed {
516 dest.write_str("to ")?;
517 }
518 k.to_css(dest)
519 }
520 }
521 LineDirection::Corner { horizontal, vertical } => {
522 if !is_prefixed {
523 dest.write_str("to ")?;
524 }
525 vertical.to_css(dest)?;
526 dest.write_char(' ')?;
527 horizontal.to_css(dest)
528 }
529 }
530 }
531}
532
533#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
537#[cfg_attr(feature = "visitor", derive(Visit))]
538#[cfg_attr(
539 feature = "serde",
540 derive(serde::Serialize, serde::Deserialize),
541 serde(tag = "type", content = "value", rename_all = "kebab-case")
542)]
543#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
544pub enum EndingShape {
545 Ellipse(Ellipse),
548 Circle(Circle),
550}
551
552impl Default for EndingShape {
553 fn default() -> EndingShape {
554 EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
555 }
556}
557
558#[derive(Debug, Clone, PartialEq)]
562#[cfg_attr(feature = "visitor", derive(Visit))]
563#[cfg_attr(
564 feature = "serde",
565 derive(serde::Serialize, serde::Deserialize),
566 serde(tag = "type", content = "value", rename_all = "kebab-case")
567)]
568#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
569pub enum Circle {
570 Radius(Length),
572 Extent(ShapeExtent),
574}
575
576impl<'i> Parse<'i> for Circle {
577 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
578 if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
579 input.expect_ident_matching("circle")?;
581 return Ok(Circle::Extent(extent));
582 }
583
584 if let Ok(length) = input.try_parse(Length::parse) {
585 let _ = input.try_parse(|input| input.expect_ident_matching("circle"));
588 return Ok(Circle::Radius(length));
589 }
590
591 if input.try_parse(|input| input.expect_ident_matching("circle")).is_ok() {
592 if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
593 return Ok(Circle::Extent(extent));
594 }
595
596 if let Ok(length) = input.try_parse(Length::parse) {
597 return Ok(Circle::Radius(length));
598 }
599
600 return Ok(Circle::Extent(ShapeExtent::FarthestCorner));
602 }
603
604 return Err(input.new_error_for_next_token());
605 }
606}
607
608impl ToCss for Circle {
609 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
610 where
611 W: std::fmt::Write,
612 {
613 match self {
614 Circle::Radius(r) => r.to_css(dest),
615 Circle::Extent(extent) => {
616 dest.write_str("circle")?;
617 if *extent != ShapeExtent::FarthestCorner {
618 dest.write_char(' ')?;
619 extent.to_css(dest)?;
620 }
621 Ok(())
622 }
623 }
624 }
625}
626
627#[derive(Debug, Clone, PartialEq)]
631#[cfg_attr(feature = "visitor", derive(Visit))]
632#[cfg_attr(
633 feature = "serde",
634 derive(serde::Serialize, serde::Deserialize),
635 serde(tag = "type", rename_all = "kebab-case")
636)]
637#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
638pub enum Ellipse {
639 Size {
641 x: LengthPercentage,
643 y: LengthPercentage,
645 },
646 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<ShapeExtent>"))]
648 Extent(ShapeExtent),
649}
650
651impl<'i> Parse<'i> for Ellipse {
652 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
653 if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
654 if input.try_parse(|input| input.expect_ident_matching("circle")).is_ok() {
657 return Err(input.new_error_for_next_token());
658 }
659 let _ = input.try_parse(|input| input.expect_ident_matching("ellipse"));
660 return Ok(Ellipse::Extent(extent));
661 }
662
663 if let Ok(x) = input.try_parse(LengthPercentage::parse) {
664 let y = LengthPercentage::parse(input)?;
665 let _ = input.try_parse(|input| input.expect_ident_matching("ellipse"));
667 return Ok(Ellipse::Size { x, y });
668 }
669
670 if input.try_parse(|input| input.expect_ident_matching("ellipse")).is_ok() {
671 if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
672 return Ok(Ellipse::Extent(extent));
673 }
674
675 if let Ok(x) = input.try_parse(LengthPercentage::parse) {
676 let y = LengthPercentage::parse(input)?;
677 return Ok(Ellipse::Size { x, y });
678 }
679
680 return Ok(Ellipse::Extent(ShapeExtent::FarthestCorner));
682 }
683
684 return Err(input.new_error_for_next_token());
685 }
686}
687
688impl ToCss for Ellipse {
689 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
690 where
691 W: std::fmt::Write,
692 {
693 match self {
695 Ellipse::Size { x, y } => {
696 x.to_css(dest)?;
697 dest.write_char(' ')?;
698 y.to_css(dest)
699 }
700 Ellipse::Extent(extent) => extent.to_css(dest),
701 }
702 }
703}
704
705enum_property! {
706 pub enum ShapeExtent {
710 ClosestSide,
712 FarthestSide,
714 ClosestCorner,
716 FarthestCorner,
718 }
719}
720
721#[derive(Debug, Clone, PartialEq)]
723#[cfg_attr(feature = "visitor", derive(Visit))]
724#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
725#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
726#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
727pub struct ConicGradient {
728 pub angle: Angle,
730 pub position: Position,
732 pub items: Vec<GradientItem<AnglePercentage>>,
734}
735
736impl ConicGradient {
737 fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
738 let angle = input.try_parse(|input| {
739 input.expect_ident_matching("from")?;
740 Angle::parse_with_unitless_zero(input)
743 });
744
745 let position = input.try_parse(|input| {
746 input.expect_ident_matching("at")?;
747 Position::parse(input)
748 });
749
750 if angle.is_ok() || position.is_ok() {
751 input.expect_comma()?;
752 }
753
754 let items = parse_items(input)?;
755 Ok(ConicGradient {
756 angle: angle.unwrap_or(Angle::Deg(0.0)),
757 position: position.unwrap_or(Position::center()),
758 items,
759 })
760 }
761}
762
763impl ToCss for ConicGradient {
764 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
765 where
766 W: std::fmt::Write,
767 {
768 if !self.angle.is_zero() {
769 dest.write_str("from ")?;
770 self.angle.to_css(dest)?;
771
772 if self.position.is_center() {
773 dest.delim(',', false)?;
774 } else {
775 dest.write_char(' ')?;
776 }
777 }
778
779 if !self.position.is_center() {
780 dest.write_str("at ")?;
781 self.position.to_css(dest)?;
782 dest.delim(',', false)?;
783 }
784
785 serialize_items(&self.items, dest)
786 }
787}
788
789impl ConicGradient {
790 fn get_fallback(&self, kind: ColorFallbackKind) -> ConicGradient {
791 ConicGradient {
792 angle: self.angle.clone(),
793 position: self.position.clone(),
794 items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
795 }
796 }
797}
798
799impl IsCompatible for ConicGradient {
800 fn is_compatible(&self, browsers: Browsers) -> bool {
801 self.items.iter().all(|item| item.is_compatible(browsers))
802 }
803}
804
805#[derive(Debug, Clone, PartialEq)]
810#[cfg_attr(feature = "visitor", derive(Visit))]
811#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
812#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
813pub struct ColorStop<D> {
814 pub color: CssColor,
816 pub position: Option<D>,
818}
819
820impl<'i, D: Parse<'i>> Parse<'i> for ColorStop<D> {
821 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
822 let color = CssColor::parse(input)?;
823 let position = input.try_parse(D::parse).ok();
824 Ok(ColorStop { color, position })
825 }
826}
827
828impl<D: ToCss> ToCss for ColorStop<D> {
829 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
830 where
831 W: std::fmt::Write,
832 {
833 self.color.to_css(dest)?;
834 if let Some(position) = &self.position {
835 dest.write_char(' ')?;
836 position.to_css(dest)?;
837 }
838 Ok(())
839 }
840}
841
842#[derive(Debug, Clone, PartialEq)]
847#[cfg_attr(feature = "visitor", derive(Visit))]
848#[cfg_attr(
849 feature = "serde",
850 derive(serde::Serialize, serde::Deserialize),
851 serde(tag = "type", rename_all = "kebab-case")
852)]
853#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
854pub enum GradientItem<D> {
855 ColorStop(ColorStop<D>),
857 #[cfg_attr(
859 feature = "serde",
860 serde(
861 bound(serialize = "D: serde::Serialize", deserialize = "D: serde::Deserialize<'de>"),
862 with = "ValueWrapper::<D>"
863 )
864 )]
865 Hint(D),
866}
867
868impl<D: ToCss> ToCss for GradientItem<D> {
869 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
870 where
871 W: std::fmt::Write,
872 {
873 match self {
874 GradientItem::ColorStop(stop) => stop.to_css(dest),
875 GradientItem::Hint(hint) => hint.to_css(dest),
876 }
877 }
878}
879
880impl<D: Clone> GradientItem<D> {
881 pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
883 match self {
884 GradientItem::ColorStop(stop) => stop.color.get_necessary_fallbacks(targets),
885 GradientItem::Hint(..) => ColorFallbackKind::empty(),
886 }
887 }
888
889 pub fn get_fallback(&self, kind: ColorFallbackKind) -> GradientItem<D> {
891 match self {
892 GradientItem::ColorStop(stop) => GradientItem::ColorStop(ColorStop {
893 color: stop.color.get_fallback(kind),
894 position: stop.position.clone(),
895 }),
896 GradientItem::Hint(..) => self.clone(),
897 }
898 }
899}
900
901impl<D> IsCompatible for GradientItem<D> {
902 fn is_compatible(&self, browsers: Browsers) -> bool {
903 match self {
904 GradientItem::ColorStop(c) => c.color.is_compatible(browsers),
905 GradientItem::Hint(..) => compat::Feature::GradientInterpolationHints.is_compatible(browsers),
906 }
907 }
908}
909
910fn parse_items<'i, 't, D: Parse<'i>>(
911 input: &mut Parser<'i, 't>,
912) -> Result<Vec<GradientItem<D>>, ParseError<'i, ParserError<'i>>> {
913 let mut items = Vec::new();
914 let mut seen_stop = false;
915
916 loop {
917 input.parse_until_before(Delimiter::Comma, |input| {
918 if seen_stop {
919 if let Ok(hint) = input.try_parse(D::parse) {
920 seen_stop = false;
921 items.push(GradientItem::Hint(hint));
922 return Ok(());
923 }
924 }
925
926 let stop = ColorStop::parse(input)?;
927
928 if let Ok(position) = input.try_parse(D::parse) {
929 let color = stop.color.clone();
930 items.push(GradientItem::ColorStop(stop));
931
932 items.push(GradientItem::ColorStop(ColorStop {
933 color,
934 position: Some(position),
935 }))
936 } else {
937 items.push(GradientItem::ColorStop(stop));
938 }
939
940 seen_stop = true;
941 Ok(())
942 })?;
943
944 match input.next() {
945 Err(_) => break,
946 Ok(Token::Comma) => continue,
947 _ => unreachable!(),
948 }
949 }
950
951 Ok(items)
952}
953
954fn serialize_items<
955 D: ToCss + std::cmp::PartialEq<D> + std::ops::Mul<f32, Output = D> + TrySign + Clone + std::fmt::Debug,
956 W,
957>(
958 items: &Vec<GradientItem<DimensionPercentage<D>>>,
959 dest: &mut Printer<W>,
960) -> Result<(), PrinterError>
961where
962 W: std::fmt::Write,
963{
964 let mut first = true;
965 let mut last: Option<&GradientItem<DimensionPercentage<D>>> = None;
966 for item in items {
967 if *item == GradientItem::Hint(DimensionPercentage::Percentage(Percentage(0.5))) {
969 continue;
970 }
971
972 if let Some(prev) = last {
974 if !should_compile!(dest.targets, DoublePositionGradients) {
975 match (prev, item) {
976 (
977 GradientItem::ColorStop(ColorStop {
978 position: Some(_),
979 color: ca,
980 }),
981 GradientItem::ColorStop(ColorStop {
982 position: Some(p),
983 color: cb,
984 }),
985 ) if ca == cb => {
986 dest.write_char(' ')?;
987 p.to_css(dest)?;
988 last = None;
989 continue;
990 }
991 _ => {}
992 }
993 }
994 }
995
996 if first {
997 first = false;
998 } else {
999 dest.delim(',', false)?;
1000 }
1001 item.to_css(dest)?;
1002 last = Some(item)
1003 }
1004 Ok(())
1005}
1006
1007#[derive(Debug, Clone, PartialEq)]
1009#[cfg_attr(feature = "visitor", derive(Visit))]
1010#[cfg_attr(
1011 feature = "serde",
1012 derive(serde::Serialize, serde::Deserialize),
1013 serde(tag = "kind", rename_all = "kebab-case")
1014)]
1015#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1016#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1017pub enum WebKitGradient {
1018 Linear {
1020 from: WebKitGradientPoint,
1022 to: WebKitGradientPoint,
1024 stops: Vec<WebKitColorStop>,
1026 },
1027 Radial {
1029 from: WebKitGradientPoint,
1031 r0: CSSNumber,
1033 to: WebKitGradientPoint,
1035 r1: CSSNumber,
1037 stops: Vec<WebKitColorStop>,
1039 },
1040}
1041
1042impl<'i> Parse<'i> for WebKitGradient {
1043 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1044 let location = input.current_source_location();
1045 let ident = input.expect_ident_cloned()?;
1046 input.expect_comma()?;
1047
1048 match_ignore_ascii_case! { &ident,
1049 "linear" => {
1050 let from = WebKitGradientPoint::parse(input)?;
1051 input.expect_comma()?;
1052 let to = WebKitGradientPoint::parse(input)?;
1053 input.expect_comma()?;
1054 let stops = input.parse_comma_separated(WebKitColorStop::parse)?;
1055 Ok(WebKitGradient::Linear {
1056 from,
1057 to,
1058 stops
1059 })
1060 },
1061 "radial" => {
1062 let from = WebKitGradientPoint::parse(input)?;
1063 input.expect_comma()?;
1064 let r0 = CSSNumber::parse(input)?;
1065 input.expect_comma()?;
1066 let to = WebKitGradientPoint::parse(input)?;
1067 input.expect_comma()?;
1068 let r1 = CSSNumber::parse(input)?;
1069 input.expect_comma()?;
1070 let stops = input.parse_comma_separated(WebKitColorStop::parse)?;
1071 Ok(WebKitGradient::Radial {
1072 from,
1073 r0,
1074 to,
1075 r1,
1076 stops
1077 })
1078 },
1079 _ => Err(location.new_unexpected_token_error(cssparser::Token::Ident(ident.clone())))
1080 }
1081 }
1082}
1083
1084impl ToCss for WebKitGradient {
1085 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1086 where
1087 W: std::fmt::Write,
1088 {
1089 match self {
1090 WebKitGradient::Linear { from, to, stops } => {
1091 dest.write_str("linear")?;
1092 dest.delim(',', false)?;
1093 from.to_css(dest)?;
1094 dest.delim(',', false)?;
1095 to.to_css(dest)?;
1096 for stop in stops {
1097 dest.delim(',', false)?;
1098 stop.to_css(dest)?;
1099 }
1100 Ok(())
1101 }
1102 WebKitGradient::Radial {
1103 from,
1104 r0,
1105 to,
1106 r1,
1107 stops,
1108 } => {
1109 dest.write_str("radial")?;
1110 dest.delim(',', false)?;
1111 from.to_css(dest)?;
1112 dest.delim(',', false)?;
1113 r0.to_css(dest)?;
1114 dest.delim(',', false)?;
1115 to.to_css(dest)?;
1116 dest.delim(',', false)?;
1117 r1.to_css(dest)?;
1118 for stop in stops {
1119 dest.delim(',', false)?;
1120 stop.to_css(dest)?;
1121 }
1122 Ok(())
1123 }
1124 }
1125 }
1126}
1127
1128impl WebKitGradient {
1129 fn get_fallback(&self, kind: ColorFallbackKind) -> WebKitGradient {
1130 let stops = match self {
1131 WebKitGradient::Linear { stops, .. } => stops,
1132 WebKitGradient::Radial { stops, .. } => stops,
1133 };
1134
1135 let stops = stops.iter().map(|stop| stop.get_fallback(kind)).collect();
1136
1137 match self {
1138 WebKitGradient::Linear { from, to, .. } => WebKitGradient::Linear {
1139 from: from.clone(),
1140 to: to.clone(),
1141 stops,
1142 },
1143 WebKitGradient::Radial { from, r0, to, r1, .. } => WebKitGradient::Radial {
1144 from: from.clone(),
1145 r0: *r0,
1146 to: to.clone(),
1147 r1: *r1,
1148 stops,
1149 },
1150 }
1151 }
1152}
1153
1154#[derive(Debug, Clone, PartialEq)]
1156#[cfg_attr(feature = "visitor", derive(Visit))]
1157#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1158#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1159pub struct WebKitColorStop {
1160 pub color: CssColor,
1162 pub position: CSSNumber,
1164}
1165
1166impl<'i> Parse<'i> for WebKitColorStop {
1167 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1168 let location = input.current_source_location();
1169 let function = input.expect_function()?.clone();
1170 input.parse_nested_block(|input| {
1171 let position = match_ignore_ascii_case! { &function,
1172 "color-stop" => {
1173 let p = NumberOrPercentage::parse(input)?;
1174 input.expect_comma()?;
1175 (&p).into()
1176 },
1177 "from" => 0.0,
1178 "to" => 1.0,
1179 _ => return Err(location.new_unexpected_token_error(cssparser::Token::Ident(function.clone())))
1180 };
1181 let color = CssColor::parse(input)?;
1182 Ok(WebKitColorStop { color, position })
1183 })
1184 }
1185}
1186
1187impl ToCss for WebKitColorStop {
1188 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1189 where
1190 W: std::fmt::Write,
1191 {
1192 if self.position == 0.0 {
1193 dest.write_str("from(")?;
1194 self.color.to_css(dest)?;
1195 } else if self.position == 1.0 {
1196 dest.write_str("to(")?;
1197 self.color.to_css(dest)?;
1198 } else {
1199 dest.write_str("color-stop(")?;
1200 self.position.to_css(dest)?;
1201 dest.delim(',', false)?;
1202 self.color.to_css(dest)?;
1203 }
1204 dest.write_char(')')
1205 }
1206}
1207
1208impl WebKitColorStop {
1209 fn get_fallback(&self, kind: ColorFallbackKind) -> WebKitColorStop {
1210 WebKitColorStop {
1211 color: self.color.get_fallback(kind),
1212 position: self.position,
1213 }
1214 }
1215}
1216
1217#[derive(Debug, Clone, PartialEq)]
1219#[cfg_attr(feature = "visitor", derive(Visit))]
1220#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1221#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1222#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1223pub struct WebKitGradientPoint {
1224 pub x: WebKitGradientPointComponent<HorizontalPositionKeyword>,
1226 pub y: WebKitGradientPointComponent<VerticalPositionKeyword>,
1228}
1229
1230impl<'i> Parse<'i> for WebKitGradientPoint {
1231 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1232 let x = WebKitGradientPointComponent::parse(input)?;
1233 let y = WebKitGradientPointComponent::parse(input)?;
1234 Ok(WebKitGradientPoint { x, y })
1235 }
1236}
1237
1238impl ToCss for WebKitGradientPoint {
1239 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1240 where
1241 W: std::fmt::Write,
1242 {
1243 self.x.to_css(dest)?;
1244 dest.write_char(' ')?;
1245 self.y.to_css(dest)
1246 }
1247}
1248
1249#[derive(Debug, Clone, PartialEq)]
1251#[cfg_attr(feature = "visitor", derive(Visit))]
1252#[cfg_attr(
1253 feature = "serde",
1254 derive(serde::Serialize, serde::Deserialize),
1255 serde(tag = "type", content = "value", rename_all = "kebab-case")
1256)]
1257#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1258#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1259pub enum WebKitGradientPointComponent<S> {
1260 Center,
1262 Number(NumberOrPercentage),
1264 Side(S),
1266}
1267
1268impl<'i, S: Parse<'i>> Parse<'i> for WebKitGradientPointComponent<S> {
1269 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1270 if input.try_parse(|i| i.expect_ident_matching("center")).is_ok() {
1271 return Ok(WebKitGradientPointComponent::Center);
1272 }
1273
1274 if let Ok(lp) = input.try_parse(NumberOrPercentage::parse) {
1275 return Ok(WebKitGradientPointComponent::Number(lp));
1276 }
1277
1278 let keyword = S::parse(input)?;
1279 Ok(WebKitGradientPointComponent::Side(keyword))
1280 }
1281}
1282
1283impl<S: ToCss + Clone + Into<LengthPercentage>> ToCss for WebKitGradientPointComponent<S> {
1284 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1285 where
1286 W: std::fmt::Write,
1287 {
1288 use WebKitGradientPointComponent::*;
1289 match &self {
1290 Center => {
1291 if dest.minify {
1292 dest.write_str("50%")
1293 } else {
1294 dest.write_str("center")
1295 }
1296 }
1297 Number(lp) => {
1298 if matches!(lp, NumberOrPercentage::Percentage(Percentage(p)) if *p == 0.0) {
1299 dest.write_char('0')
1300 } else {
1301 lp.to_css(dest)
1302 }
1303 }
1304 Side(s) => {
1305 if dest.minify {
1306 let lp: LengthPercentage = s.clone().into();
1307 lp.to_css(dest)?;
1308 } else {
1309 s.to_css(dest)?;
1310 }
1311 Ok(())
1312 }
1313 }
1314 }
1315}
1316
1317impl<S: Clone> WebKitGradientPointComponent<S> {
1318 fn from_position(pos: &PositionComponent<S>) -> Result<WebKitGradientPointComponent<S>, ()> {
1320 match pos {
1321 PositionComponent::Center => Ok(WebKitGradientPointComponent::Center),
1322 PositionComponent::Length(len) => {
1323 Ok(WebKitGradientPointComponent::Number(match len {
1324 LengthPercentage::Percentage(p) => NumberOrPercentage::Percentage(p.clone()),
1325 LengthPercentage::Dimension(d) => {
1326 if let Some(px) = d.to_px() {
1328 NumberOrPercentage::Number(px)
1329 } else {
1330 return Err(());
1331 }
1332 }
1333 _ => return Err(()),
1334 }))
1335 }
1336 PositionComponent::Side { side, offset } => {
1337 if offset.is_some() {
1338 return Err(());
1339 }
1340 Ok(WebKitGradientPointComponent::Side(side.clone()))
1341 }
1342 }
1343 }
1344}
1345
1346impl WebKitGradient {
1347 pub fn from_standard(gradient: &Gradient) -> Result<WebKitGradient, ()> {
1349 match gradient {
1350 Gradient::Linear(linear) => {
1351 let (from, to) = match &linear.direction {
1353 LineDirection::Horizontal(horizontal) => match horizontal {
1354 HorizontalPositionKeyword::Left => ((1.0, 0.0), (0.0, 0.0)),
1355 HorizontalPositionKeyword::Right => ((0.0, 0.0), (1.0, 0.0)),
1356 },
1357 LineDirection::Vertical(vertical) => match vertical {
1358 VerticalPositionKeyword::Top => ((0.0, 1.0), (0.0, 0.0)),
1359 VerticalPositionKeyword::Bottom => ((0.0, 0.0), (0.0, 1.0)),
1360 },
1361 LineDirection::Corner { horizontal, vertical } => match (horizontal, vertical) {
1362 (HorizontalPositionKeyword::Left, VerticalPositionKeyword::Top) => ((1.0, 1.0), (0.0, 0.0)),
1363 (HorizontalPositionKeyword::Left, VerticalPositionKeyword::Bottom) => ((1.0, 0.0), (0.0, 1.0)),
1364 (HorizontalPositionKeyword::Right, VerticalPositionKeyword::Top) => ((0.0, 1.0), (1.0, 0.0)),
1365 (HorizontalPositionKeyword::Right, VerticalPositionKeyword::Bottom) => ((0.0, 0.0), (1.0, 1.0)),
1366 },
1367 LineDirection::Angle(angle) => {
1368 let degrees = angle.to_degrees();
1369 if degrees == 0.0 {
1370 ((0.0, 1.0), (0.0, 0.0))
1371 } else if degrees == 90.0 {
1372 ((0.0, 0.0), (1.0, 0.0))
1373 } else if degrees == 180.0 {
1374 ((0.0, 0.0), (0.0, 1.0))
1375 } else if degrees == 270.0 {
1376 ((1.0, 0.0), (0.0, 0.0))
1377 } else {
1378 return Err(());
1379 }
1380 }
1381 };
1382
1383 Ok(WebKitGradient::Linear {
1384 from: WebKitGradientPoint {
1385 x: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(from.0))),
1386 y: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(from.1))),
1387 },
1388 to: WebKitGradientPoint {
1389 x: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(to.0))),
1390 y: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(to.1))),
1391 },
1392 stops: convert_stops_to_webkit(&linear.items)?,
1393 })
1394 }
1395 Gradient::Radial(radial) => {
1396 let radius = match &radial.shape {
1398 EndingShape::Circle(Circle::Radius(radius)) => {
1399 if let Some(r) = radius.to_px() {
1400 r
1401 } else {
1402 return Err(());
1403 }
1404 }
1405 _ => return Err(()),
1406 };
1407
1408 let x = WebKitGradientPointComponent::from_position(&radial.position.x)?;
1409 let y = WebKitGradientPointComponent::from_position(&radial.position.y)?;
1410 let point = WebKitGradientPoint { x, y };
1411 Ok(WebKitGradient::Radial {
1412 from: point.clone(),
1413 r0: 0.0,
1414 to: point,
1415 r1: radius,
1416 stops: convert_stops_to_webkit(&radial.items)?,
1417 })
1418 }
1419 _ => Err(()),
1420 }
1421 }
1422}
1423
1424fn convert_stops_to_webkit(items: &Vec<GradientItem<LengthPercentage>>) -> Result<Vec<WebKitColorStop>, ()> {
1425 let mut stops = Vec::with_capacity(items.len());
1426 for (i, item) in items.iter().enumerate() {
1427 match item {
1428 GradientItem::ColorStop(stop) => {
1429 let position = if let Some(pos) = &stop.position {
1431 if let LengthPercentage::Percentage(position) = pos {
1432 position.0
1433 } else {
1434 return Err(());
1435 }
1436 } else if i == 0 {
1437 0.0
1438 } else if i == items.len() - 1 {
1439 1.0
1440 } else {
1441 return Err(());
1442 };
1443
1444 stops.push(WebKitColorStop {
1445 color: stop.color.clone(),
1446 position,
1447 })
1448 }
1449 _ => return Err(()),
1450 }
1451 }
1452
1453 Ok(stops)
1454}