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