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::Browsers;
16use crate::traits::{Parse, ToCss, TrySign, Zero};
17use crate::vendor_prefix::VendorPrefix;
18use cssparser::*;
19
20#[derive(Debug, Clone, PartialEq)]
22#[cfg_attr(
23 feature = "serde",
24 derive(serde::Serialize, serde::Deserialize),
25 serde(tag = "type", content = "value", rename_all = "kebab-case")
26)]
27pub enum Gradient {
28 Linear(LinearGradient, VendorPrefix),
30 RepeatingLinear(LinearGradient, VendorPrefix),
32 Radial(RadialGradient, VendorPrefix),
34 RepeatingRadial(RadialGradient, VendorPrefix),
36 Conic(ConicGradient),
38 RepeatingConic(ConicGradient),
40 WebKitGradient(WebKitGradient),
42}
43
44impl Gradient {
45 pub fn get_vendor_prefix(&self) -> VendorPrefix {
47 match self {
48 Gradient::Linear(_, prefix)
49 | Gradient::RepeatingLinear(_, prefix)
50 | Gradient::Radial(_, prefix)
51 | Gradient::RepeatingRadial(_, prefix) => *prefix,
52 Gradient::WebKitGradient(_) => VendorPrefix::WebKit,
53 _ => VendorPrefix::None,
54 }
55 }
56
57 pub fn get_necessary_prefixes(&self, targets: Browsers) -> VendorPrefix {
59 macro_rules! get_prefixes {
60 ($feature: ident, $prefix: ident) => {
61 if *$prefix == VendorPrefix::None {
62 Feature::$feature.prefixes_for(targets)
63 } else {
64 *$prefix
65 }
66 };
67 }
68
69 match self {
70 Gradient::Linear(_, prefix) => get_prefixes!(LinearGradient, prefix),
71 Gradient::RepeatingLinear(_, prefix) => get_prefixes!(RepeatingLinearGradient, prefix),
72 Gradient::Radial(_, prefix) => get_prefixes!(RadialGradient, prefix),
73 Gradient::RepeatingRadial(_, prefix) => get_prefixes!(RepeatingRadialGradient, prefix),
74 _ => VendorPrefix::None,
75 }
76 }
77
78 pub fn get_prefixed(&self, prefix: VendorPrefix) -> Gradient {
80 match self {
81 Gradient::Linear(linear, _) => Gradient::Linear(linear.clone(), prefix),
82 Gradient::RepeatingLinear(linear, _) => Gradient::RepeatingLinear(linear.clone(), prefix),
83 Gradient::Radial(radial, _) => Gradient::Radial(radial.clone(), prefix),
84 Gradient::RepeatingRadial(radial, _) => Gradient::RepeatingRadial(radial.clone(), prefix),
85 _ => self.clone(),
86 }
87 }
88
89 pub fn get_legacy_webkit(&self) -> Result<Gradient, ()> {
93 Ok(Gradient::WebKitGradient(WebKitGradient::from_standard(self)?))
94 }
95
96 pub fn get_necessary_fallbacks(&self, targets: Browsers) -> ColorFallbackKind {
98 match self {
99 Gradient::Linear(LinearGradient { items, .. }, _)
100 | Gradient::Radial(RadialGradient { items, .. }, _)
101 | Gradient::RepeatingLinear(LinearGradient { items, .. }, _)
102 | Gradient::RepeatingRadial(RadialGradient { items, .. }, _) => {
103 let mut fallbacks = ColorFallbackKind::empty();
104 for item in items {
105 fallbacks |= item.get_necessary_fallbacks(targets)
106 }
107 fallbacks
108 }
109 Gradient::Conic(ConicGradient { items, .. }) | Gradient::RepeatingConic(ConicGradient { items, .. }) => {
110 let mut fallbacks = ColorFallbackKind::empty();
111 for item in items {
112 fallbacks |= item.get_necessary_fallbacks(targets)
113 }
114 fallbacks
115 }
116 Gradient::WebKitGradient(..) => ColorFallbackKind::empty(),
117 }
118 }
119
120 pub fn get_fallback(&self, kind: ColorFallbackKind) -> Gradient {
122 match self {
123 Gradient::Linear(g, prefixes) | Gradient::RepeatingLinear(g, prefixes) => {
124 Gradient::Linear(g.get_fallback(kind), *prefixes)
125 }
126 Gradient::Radial(g, prefixes) | Gradient::RepeatingRadial(g, prefixes) => {
127 Gradient::Radial(g.get_fallback(kind), *prefixes)
128 }
129 Gradient::Conic(g) | Gradient::RepeatingConic(g) => Gradient::Conic(g.get_fallback(kind)),
130 Gradient::WebKitGradient(g) => Gradient::WebKitGradient(g.get_fallback(kind)),
131 }
132 }
133}
134
135impl<'i> Parse<'i> for Gradient {
136 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
137 let location = input.current_source_location();
138 let func = input.expect_function()?.clone();
139 input.parse_nested_block(|input| {
140 match_ignore_ascii_case! { &func,
141 "linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, false)?, VendorPrefix::None)),
142 "repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, false)?, VendorPrefix::None)),
143 "radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input)?, VendorPrefix::None)),
144 "repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input)?, VendorPrefix::None)),
145 "conic-gradient" => Ok(Gradient::Conic(ConicGradient::parse(input)?)),
146 "repeating-conic-gradient" => Ok(Gradient::RepeatingConic(ConicGradient::parse(input)?)),
147 "-webkit-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, true)?, VendorPrefix::WebKit)),
148 "-webkit-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, true)?, VendorPrefix::WebKit)),
149 "-webkit-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input)?, VendorPrefix::WebKit)),
150 "-webkit-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input)?, VendorPrefix::WebKit)),
151 "-moz-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, true)?, VendorPrefix::Moz)),
152 "-moz-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, true)?, VendorPrefix::Moz)),
153 "-moz-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input)?, VendorPrefix::Moz)),
154 "-moz-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input)?, VendorPrefix::Moz)),
155 "-o-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, true)?, VendorPrefix::O)),
156 "-o-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, true)?, VendorPrefix::O)),
157 "-o-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input)?, VendorPrefix::O)),
158 "-o-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input)?, VendorPrefix::O)),
159 "-webkit-gradient" => Ok(Gradient::WebKitGradient(WebKitGradient::parse(input)?)),
160 _ => Err(location.new_unexpected_token_error(cssparser::Token::Ident(func.clone())))
161 }
162 })
163 }
164}
165
166impl ToCss for Gradient {
167 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
168 where
169 W: std::fmt::Write,
170 {
171 let (f, prefix) = match self {
172 Gradient::Linear(_, prefix) => ("linear-gradient(", Some(prefix)),
173 Gradient::RepeatingLinear(_, prefix) => ("repeating-linear-gradient(", Some(prefix)),
174 Gradient::Radial(_, prefix) => ("radial-gradient(", Some(prefix)),
175 Gradient::RepeatingRadial(_, prefix) => ("repeating-radial-gradient(", Some(prefix)),
176 Gradient::Conic(_) => ("conic-gradient(", None),
177 Gradient::RepeatingConic(_) => ("repeating-conic-gradient(", None),
178 Gradient::WebKitGradient(_) => ("-webkit-gradient(", None),
179 };
180
181 if let Some(prefix) = prefix {
182 prefix.to_css(dest)?;
183 }
184
185 dest.write_str(f)?;
186
187 match self {
188 Gradient::Linear(linear, prefix) | Gradient::RepeatingLinear(linear, prefix) => {
189 linear.to_css(dest, *prefix != VendorPrefix::None)?
190 }
191 Gradient::Radial(radial, _) | Gradient::RepeatingRadial(radial, _) => radial.to_css(dest)?,
192 Gradient::Conic(conic) | Gradient::RepeatingConic(conic) => conic.to_css(dest)?,
193 Gradient::WebKitGradient(g) => g.to_css(dest)?,
194 }
195
196 dest.write_char(')')
197 }
198}
199
200#[derive(Debug, Clone, PartialEq)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203pub struct LinearGradient {
204 pub direction: LineDirection,
206 pub items: Vec<GradientItem<LengthPercentage>>,
208}
209
210impl LinearGradient {
211 fn parse<'i, 't>(
212 input: &mut Parser<'i, 't>,
213 is_prefixed: bool,
214 ) -> Result<LinearGradient, ParseError<'i, ParserError<'i>>> {
215 let direction = if let Ok(direction) = input.try_parse(|input| LineDirection::parse(input, is_prefixed)) {
216 input.expect_comma()?;
217 direction
218 } else {
219 LineDirection::Vertical(VerticalPositionKeyword::Bottom)
220 };
221 let items = parse_items(input)?;
222 Ok(LinearGradient { direction, items })
223 }
224
225 fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>
226 where
227 W: std::fmt::Write,
228 {
229 let angle = match &self.direction {
230 LineDirection::Vertical(VerticalPositionKeyword::Bottom) => 180.0,
231 LineDirection::Vertical(VerticalPositionKeyword::Top) => 0.0,
232 LineDirection::Angle(angle) => angle.to_degrees(),
233 _ => -1.0,
234 };
235
236 if angle == 180.0 {
238 serialize_items(&self.items, dest)
239
240 } else if angle == 0.0
243 && dest.minify
244 && self.items.iter().all(|item| {
245 matches!(
246 item,
247 GradientItem::Hint(LengthPercentage::Percentage(_))
248 | GradientItem::ColorStop(ColorStop {
249 position: None | Some(LengthPercentage::Percentage(_)),
250 ..
251 })
252 )
253 })
254 {
255 let items: Vec<GradientItem<LengthPercentage>> = self
256 .items
257 .iter()
258 .rev()
259 .map(|item| {
260 match item {
262 GradientItem::Hint(LengthPercentage::Percentage(p)) => {
263 GradientItem::Hint(LengthPercentage::Percentage(Percentage(1.0 - p.0)))
264 }
265 GradientItem::ColorStop(ColorStop { color, position }) => GradientItem::ColorStop(ColorStop {
266 color: color.clone(),
267 position: position.clone().map(|p| match p {
268 LengthPercentage::Percentage(p) => LengthPercentage::Percentage(Percentage(1.0 - p.0)),
269 _ => unreachable!(),
270 }),
271 }),
272 _ => unreachable!(),
273 }
274 })
275 .collect();
276 serialize_items(&items, dest)
277 } else {
278 if self.direction != LineDirection::Vertical(VerticalPositionKeyword::Bottom)
279 && self.direction != LineDirection::Angle(Angle::Deg(180.0))
280 {
281 self.direction.to_css(dest, is_prefixed)?;
282 dest.delim(',', false)?;
283 }
284
285 serialize_items(&self.items, dest)
286 }
287 }
288
289 fn get_fallback(&self, kind: ColorFallbackKind) -> LinearGradient {
290 LinearGradient {
291 direction: self.direction.clone(),
292 items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
293 }
294 }
295}
296
297#[derive(Debug, Clone, PartialEq)]
299#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
300pub struct RadialGradient {
301 pub shape: EndingShape,
303 pub position: Position,
305 pub items: Vec<GradientItem<LengthPercentage>>,
307}
308
309impl<'i> Parse<'i> for RadialGradient {
310 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<RadialGradient, ParseError<'i, ParserError<'i>>> {
311 let shape = input.try_parse(EndingShape::parse).ok();
312 let position = input
313 .try_parse(|input| {
314 input.expect_ident_matching("at")?;
315 Position::parse(input)
316 })
317 .ok();
318
319 if shape.is_some() || position.is_some() {
320 input.expect_comma()?;
321 }
322
323 let items = parse_items(input)?;
324 Ok(RadialGradient {
325 shape: shape.unwrap_or_default(),
326 position: position.unwrap_or(Position::center()),
327 items,
328 })
329 }
330}
331
332impl ToCss for RadialGradient {
333 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
334 where
335 W: std::fmt::Write,
336 {
337 if self.shape != EndingShape::default() {
338 self.shape.to_css(dest)?;
339 if self.position.is_center() {
340 dest.delim(',', false)?;
341 } else {
342 dest.write_char(' ')?;
343 }
344 }
345
346 if !self.position.is_center() {
347 dest.write_str("at ")?;
348 self.position.to_css(dest)?;
349 dest.delim(',', false)?;
350 }
351
352 serialize_items(&self.items, dest)
353 }
354}
355
356impl RadialGradient {
357 fn get_fallback(&self, kind: ColorFallbackKind) -> RadialGradient {
358 RadialGradient {
359 shape: self.shape.clone(),
360 position: self.position.clone(),
361 items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
362 }
363 }
364}
365
366#[derive(Debug, Clone, PartialEq)]
370#[cfg_attr(
371 feature = "serde",
372 derive(serde::Serialize, serde::Deserialize),
373 serde(tag = "type", content = "value", rename_all = "kebab-case")
374)]
375pub enum LineDirection {
376 Angle(Angle),
378 Horizontal(HorizontalPositionKeyword),
380 Vertical(VerticalPositionKeyword),
382 Corner(HorizontalPositionKeyword, VerticalPositionKeyword),
384}
385
386impl LineDirection {
387 fn parse<'i, 't>(
388 input: &mut Parser<'i, 't>,
389 is_prefixed: bool,
390 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
391 if let Ok(angle) = input.try_parse(Angle::parse) {
392 return Ok(LineDirection::Angle(angle));
393 }
394
395 if !is_prefixed {
396 input.expect_ident_matching("to")?;
397 }
398
399 if let Ok(x) = input.try_parse(HorizontalPositionKeyword::parse) {
400 if let Ok(y) = input.try_parse(VerticalPositionKeyword::parse) {
401 return Ok(LineDirection::Corner(x, y));
402 }
403 return Ok(LineDirection::Horizontal(x));
404 }
405
406 let y = VerticalPositionKeyword::parse(input)?;
407 if let Ok(x) = input.try_parse(HorizontalPositionKeyword::parse) {
408 return Ok(LineDirection::Corner(x, y));
409 }
410 Ok(LineDirection::Vertical(y))
411 }
412
413 fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>
414 where
415 W: std::fmt::Write,
416 {
417 match self {
418 LineDirection::Angle(angle) => angle.to_css(dest),
419 LineDirection::Horizontal(k) => {
420 if dest.minify {
421 match k {
422 HorizontalPositionKeyword::Left => dest.write_str("270deg"),
423 HorizontalPositionKeyword::Right => dest.write_str("90deg"),
424 }
425 } else {
426 if !is_prefixed {
427 dest.write_str("to ")?;
428 }
429 k.to_css(dest)
430 }
431 }
432 LineDirection::Vertical(k) => {
433 if dest.minify {
434 match k {
435 VerticalPositionKeyword::Top => dest.write_str("0deg"),
436 VerticalPositionKeyword::Bottom => dest.write_str("180deg"),
437 }
438 } else {
439 if !is_prefixed {
440 dest.write_str("to ")?;
441 }
442 k.to_css(dest)
443 }
444 }
445 LineDirection::Corner(x, y) => {
446 if !is_prefixed {
447 dest.write_str("to ")?;
448 }
449 y.to_css(dest)?;
450 dest.write_char(' ')?;
451 x.to_css(dest)
452 }
453 }
454 }
455}
456
457#[derive(Debug, Clone, PartialEq)]
461#[cfg_attr(
462 feature = "serde",
463 derive(serde::Serialize, serde::Deserialize),
464 serde(tag = "type", content = "value", rename_all = "kebab-case")
465)]
466pub enum EndingShape {
467 Circle(Circle),
469 Ellipse(Ellipse),
471}
472
473impl Default for EndingShape {
474 fn default() -> EndingShape {
475 EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
476 }
477}
478
479impl<'i> Parse<'i> for EndingShape {
480 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
481 if let Ok(ellipse) = input.try_parse(Ellipse::parse) {
483 return Ok(EndingShape::Ellipse(ellipse));
484 }
485
486 if let Ok(circle) = input.try_parse(Circle::parse) {
487 return Ok(EndingShape::Circle(circle));
488 }
489
490 return Err(input.new_error_for_next_token());
491 }
492}
493
494impl ToCss for EndingShape {
495 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
496 where
497 W: std::fmt::Write,
498 {
499 match self {
500 EndingShape::Circle(circle) => circle.to_css(dest),
501 EndingShape::Ellipse(ellipse) => ellipse.to_css(dest),
502 }
503 }
504}
505
506#[derive(Debug, Clone, PartialEq)]
510#[cfg_attr(
511 feature = "serde",
512 derive(serde::Serialize, serde::Deserialize),
513 serde(tag = "type", content = "value", rename_all = "kebab-case")
514)]
515pub enum Circle {
516 Radius(Length),
518 Extent(ShapeExtent),
520}
521
522impl<'i> Parse<'i> for Circle {
523 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
524 if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
525 input.expect_ident_matching("circle")?;
527 return Ok(Circle::Extent(extent));
528 }
529
530 if let Ok(length) = input.try_parse(Length::parse) {
531 let _ = input.try_parse(|input| input.expect_ident_matching("circle"));
534 return Ok(Circle::Radius(length));
535 }
536
537 if input.try_parse(|input| input.expect_ident_matching("circle")).is_ok() {
538 if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
539 return Ok(Circle::Extent(extent));
540 }
541
542 if let Ok(length) = input.try_parse(Length::parse) {
543 return Ok(Circle::Radius(length));
544 }
545
546 return Ok(Circle::Extent(ShapeExtent::FarthestCorner));
548 }
549
550 return Err(input.new_error_for_next_token());
551 }
552}
553
554impl ToCss for Circle {
555 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
556 where
557 W: std::fmt::Write,
558 {
559 match self {
560 Circle::Radius(r) => r.to_css(dest),
561 Circle::Extent(extent) => {
562 dest.write_str("circle")?;
563 if *extent != ShapeExtent::FarthestCorner {
564 dest.write_char(' ')?;
565 extent.to_css(dest)?;
566 }
567 Ok(())
568 }
569 }
570 }
571}
572
573#[derive(Debug, Clone, PartialEq)]
577#[cfg_attr(
578 feature = "serde",
579 derive(serde::Serialize, serde::Deserialize),
580 serde(tag = "type", content = "value", rename_all = "kebab-case")
581)]
582pub enum Ellipse {
583 Size(LengthPercentage, LengthPercentage),
585 Extent(ShapeExtent),
587}
588
589impl<'i> Parse<'i> for Ellipse {
590 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
591 if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
592 if input.try_parse(|input| input.expect_ident_matching("circle")).is_ok() {
595 return Err(input.new_error_for_next_token());
596 }
597 let _ = input.try_parse(|input| input.expect_ident_matching("ellipse"));
598 return Ok(Ellipse::Extent(extent));
599 }
600
601 if let Ok(x) = input.try_parse(LengthPercentage::parse) {
602 let y = LengthPercentage::parse(input)?;
603 let _ = input.try_parse(|input| input.expect_ident_matching("ellipse"));
605 return Ok(Ellipse::Size(x, y));
606 }
607
608 if input.try_parse(|input| input.expect_ident_matching("ellipse")).is_ok() {
609 if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
610 return Ok(Ellipse::Extent(extent));
611 }
612
613 if let Ok(x) = input.try_parse(LengthPercentage::parse) {
614 let y = LengthPercentage::parse(input)?;
615 return Ok(Ellipse::Size(x, y));
616 }
617
618 return Ok(Ellipse::Extent(ShapeExtent::FarthestCorner));
620 }
621
622 return Err(input.new_error_for_next_token());
623 }
624}
625
626impl ToCss for Ellipse {
627 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
628 where
629 W: std::fmt::Write,
630 {
631 match self {
633 Ellipse::Size(x, y) => {
634 x.to_css(dest)?;
635 dest.write_char(' ')?;
636 y.to_css(dest)
637 }
638 Ellipse::Extent(extent) => extent.to_css(dest),
639 }
640 }
641}
642
643enum_property! {
644 pub enum ShapeExtent {
648 "closest-side": ClosestSide,
650 "farthest-side": FarthestSide,
652 "closest-corner": ClosestCorner,
654 "farthest-corner": FarthestCorner,
656 }
657}
658
659#[derive(Debug, Clone, PartialEq)]
661#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
662pub struct ConicGradient {
663 pub angle: Angle,
665 pub position: Position,
667 pub items: Vec<GradientItem<AnglePercentage>>,
669}
670
671impl ConicGradient {
672 fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
673 let angle = input.try_parse(|input| {
674 input.expect_ident_matching("from")?;
675 Angle::parse(input)
676 });
677
678 let position = input.try_parse(|input| {
679 input.expect_ident_matching("at")?;
680 Position::parse(input)
681 });
682
683 if angle.is_ok() || position.is_ok() {
684 input.expect_comma()?;
685 }
686
687 let items = parse_items(input)?;
688 Ok(ConicGradient {
689 angle: angle.unwrap_or(Angle::Deg(0.0)),
690 position: position.unwrap_or(Position::center()),
691 items,
692 })
693 }
694}
695
696impl ToCss for ConicGradient {
697 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
698 where
699 W: std::fmt::Write,
700 {
701 if !self.angle.is_zero() {
702 dest.write_str("from ")?;
703 self.angle.to_css(dest)?;
704
705 if self.position.is_center() {
706 dest.delim(',', false)?;
707 } else {
708 dest.write_char(' ')?;
709 }
710 }
711
712 if !self.position.is_center() {
713 dest.write_str("at ")?;
714 self.position.to_css(dest)?;
715 dest.delim(',', false)?;
716 }
717
718 serialize_items(&self.items, dest)
719 }
720}
721
722impl ConicGradient {
723 fn get_fallback(&self, kind: ColorFallbackKind) -> ConicGradient {
724 ConicGradient {
725 angle: self.angle.clone(),
726 position: self.position.clone(),
727 items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
728 }
729 }
730}
731
732#[derive(Debug, Clone, PartialEq)]
737#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
738pub struct ColorStop<D> {
739 pub color: CssColor,
741 pub position: Option<D>,
743}
744
745impl<'i, D: Parse<'i>> Parse<'i> for ColorStop<D> {
746 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
747 let color = CssColor::parse(input)?;
748 let position = input.try_parse(D::parse).ok();
749 Ok(ColorStop { color, position })
750 }
751}
752
753impl<D: ToCss> ToCss for ColorStop<D> {
754 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
755 where
756 W: std::fmt::Write,
757 {
758 self.color.to_css(dest)?;
759 if let Some(position) = &self.position {
760 dest.write_char(' ')?;
761 position.to_css(dest)?;
762 }
763 Ok(())
764 }
765}
766
767#[derive(Debug, Clone, PartialEq)]
772#[cfg_attr(
773 feature = "serde",
774 derive(serde::Serialize, serde::Deserialize),
775 serde(tag = "type", content = "value", rename_all = "kebab-case")
776)]
777pub enum GradientItem<D> {
778 ColorStop(ColorStop<D>),
780 Hint(D),
782}
783
784impl<D: ToCss> ToCss for GradientItem<D> {
785 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
786 where
787 W: std::fmt::Write,
788 {
789 match self {
790 GradientItem::ColorStop(stop) => stop.to_css(dest),
791 GradientItem::Hint(hint) => hint.to_css(dest),
792 }
793 }
794}
795
796impl<D: Clone> GradientItem<D> {
797 pub fn get_necessary_fallbacks(&self, targets: Browsers) -> ColorFallbackKind {
799 match self {
800 GradientItem::ColorStop(stop) => stop.color.get_necessary_fallbacks(targets),
801 GradientItem::Hint(..) => ColorFallbackKind::empty(),
802 }
803 }
804
805 pub fn get_fallback(&self, kind: ColorFallbackKind) -> GradientItem<D> {
807 match self {
808 GradientItem::ColorStop(stop) => GradientItem::ColorStop(ColorStop {
809 color: stop.color.get_fallback(kind),
810 position: stop.position.clone(),
811 }),
812 GradientItem::Hint(..) => self.clone(),
813 }
814 }
815}
816
817fn parse_items<'i, 't, D: Parse<'i>>(
818 input: &mut Parser<'i, 't>,
819) -> Result<Vec<GradientItem<D>>, ParseError<'i, ParserError<'i>>> {
820 let mut items = Vec::new();
821 let mut seen_stop = false;
822
823 loop {
824 input.parse_until_before(Delimiter::Comma, |input| {
825 if seen_stop {
826 if let Ok(hint) = input.try_parse(D::parse) {
827 seen_stop = false;
828 items.push(GradientItem::Hint(hint));
829 return Ok(());
830 }
831 }
832
833 let stop = ColorStop::parse(input)?;
834
835 if let Ok(position) = input.try_parse(D::parse) {
836 let color = stop.color.clone();
837 items.push(GradientItem::ColorStop(stop));
838
839 items.push(GradientItem::ColorStop(ColorStop {
840 color,
841 position: Some(position),
842 }))
843 } else {
844 items.push(GradientItem::ColorStop(stop));
845 }
846
847 seen_stop = true;
848 Ok(())
849 })?;
850
851 match input.next() {
852 Err(_) => break,
853 Ok(Token::Comma) => continue,
854 _ => unreachable!(),
855 }
856 }
857
858 Ok(items)
859}
860
861fn serialize_items<
862 D: ToCss + std::cmp::PartialEq<D> + std::ops::Mul<f32, Output = D> + TrySign + Clone + std::fmt::Debug,
863 W,
864>(
865 items: &Vec<GradientItem<DimensionPercentage<D>>>,
866 dest: &mut Printer<W>,
867) -> Result<(), PrinterError>
868where
869 W: std::fmt::Write,
870{
871 let mut first = true;
872 let mut last: Option<&GradientItem<DimensionPercentage<D>>> = None;
873 for item in items {
874 if *item == GradientItem::Hint(DimensionPercentage::Percentage(Percentage(0.5))) {
876 continue;
877 }
878
879 if let Some(prev) = last {
881 if dest.targets.is_none() || compat::Feature::DoublePositionGradients.is_compatible(dest.targets.unwrap()) {
882 match (prev, item) {
883 (
884 GradientItem::ColorStop(ColorStop {
885 position: Some(_),
886 color: ca,
887 }),
888 GradientItem::ColorStop(ColorStop {
889 position: Some(p),
890 color: cb,
891 }),
892 ) if ca == cb => {
893 dest.write_char(' ')?;
894 p.to_css(dest)?;
895 last = None;
896 continue;
897 }
898 _ => {}
899 }
900 }
901 }
902
903 if first {
904 first = false;
905 } else {
906 dest.delim(',', false)?;
907 }
908 item.to_css(dest)?;
909 last = Some(item)
910 }
911 Ok(())
912}
913
914#[derive(Debug, Clone, PartialEq)]
916#[cfg_attr(
917 feature = "serde",
918 derive(serde::Serialize, serde::Deserialize),
919 serde(tag = "type", content = "value", rename_all = "kebab-case")
920)]
921pub enum WebKitGradient {
922 Linear {
924 from: WebKitGradientPoint,
926 to: WebKitGradientPoint,
928 stops: Vec<WebKitColorStop>,
930 },
931 Radial {
933 from: WebKitGradientPoint,
935 r0: CSSNumber,
937 to: WebKitGradientPoint,
939 r1: CSSNumber,
941 stops: Vec<WebKitColorStop>,
943 },
944}
945
946impl<'i> Parse<'i> for WebKitGradient {
947 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
948 let location = input.current_source_location();
949 let ident = input.expect_ident_cloned()?;
950 input.expect_comma()?;
951
952 match_ignore_ascii_case! { &ident,
953 "linear" => {
954 let from = WebKitGradientPoint::parse(input)?;
955 input.expect_comma()?;
956 let to = WebKitGradientPoint::parse(input)?;
957 input.expect_comma()?;
958 let stops = input.parse_comma_separated(WebKitColorStop::parse)?;
959 Ok(WebKitGradient::Linear {
960 from,
961 to,
962 stops
963 })
964 },
965 "radial" => {
966 let from = WebKitGradientPoint::parse(input)?;
967 input.expect_comma()?;
968 let r0 = CSSNumber::parse(input)?;
969 input.expect_comma()?;
970 let to = WebKitGradientPoint::parse(input)?;
971 input.expect_comma()?;
972 let r1 = CSSNumber::parse(input)?;
973 input.expect_comma()?;
974 let stops = input.parse_comma_separated(WebKitColorStop::parse)?;
975 Ok(WebKitGradient::Radial {
976 from,
977 r0,
978 to,
979 r1,
980 stops
981 })
982 },
983 _ => Err(location.new_unexpected_token_error(cssparser::Token::Ident(ident.clone())))
984 }
985 }
986}
987
988impl ToCss for WebKitGradient {
989 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
990 where
991 W: std::fmt::Write,
992 {
993 match self {
994 WebKitGradient::Linear { from, to, stops } => {
995 dest.write_str("linear")?;
996 dest.delim(',', false)?;
997 from.to_css(dest)?;
998 dest.delim(',', false)?;
999 to.to_css(dest)?;
1000 for stop in stops {
1001 dest.delim(',', false)?;
1002 stop.to_css(dest)?;
1003 }
1004 Ok(())
1005 }
1006 WebKitGradient::Radial {
1007 from,
1008 r0,
1009 to,
1010 r1,
1011 stops,
1012 } => {
1013 dest.write_str("radial")?;
1014 dest.delim(',', false)?;
1015 from.to_css(dest)?;
1016 dest.delim(',', false)?;
1017 r0.to_css(dest)?;
1018 dest.delim(',', false)?;
1019 to.to_css(dest)?;
1020 dest.delim(',', false)?;
1021 r1.to_css(dest)?;
1022 for stop in stops {
1023 dest.delim(',', false)?;
1024 stop.to_css(dest)?;
1025 }
1026 Ok(())
1027 }
1028 }
1029 }
1030}
1031
1032impl WebKitGradient {
1033 fn get_fallback(&self, kind: ColorFallbackKind) -> WebKitGradient {
1034 let stops = match self {
1035 WebKitGradient::Linear { stops, .. } => stops,
1036 WebKitGradient::Radial { stops, .. } => stops,
1037 };
1038
1039 let stops = stops.iter().map(|stop| stop.get_fallback(kind)).collect();
1040
1041 match self {
1042 WebKitGradient::Linear { from, to, .. } => WebKitGradient::Linear {
1043 from: from.clone(),
1044 to: to.clone(),
1045 stops,
1046 },
1047 WebKitGradient::Radial { from, r0, to, r1, .. } => WebKitGradient::Radial {
1048 from: from.clone(),
1049 r0: *r0,
1050 to: to.clone(),
1051 r1: *r1,
1052 stops,
1053 },
1054 }
1055 }
1056}
1057
1058#[derive(Debug, Clone, PartialEq)]
1060#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1061pub struct WebKitColorStop {
1062 pub color: CssColor,
1064 pub position: CSSNumber,
1066}
1067
1068impl<'i> Parse<'i> for WebKitColorStop {
1069 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1070 let location = input.current_source_location();
1071 let function = input.expect_function()?.clone();
1072 input.parse_nested_block(|input| {
1073 let position = match_ignore_ascii_case! { &function,
1074 "color-stop" => {
1075 let p = NumberOrPercentage::parse(input)?;
1076 input.expect_comma()?;
1077 (&p).into()
1078 },
1079 "from" => 0.0,
1080 "to" => 1.0,
1081 _ => return Err(location.new_unexpected_token_error(cssparser::Token::Ident(function.clone())))
1082 };
1083 let color = CssColor::parse(input)?;
1084 Ok(WebKitColorStop { color, position })
1085 })
1086 }
1087}
1088
1089impl ToCss for WebKitColorStop {
1090 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1091 where
1092 W: std::fmt::Write,
1093 {
1094 if self.position == 0.0 {
1095 dest.write_str("from(")?;
1096 self.color.to_css(dest)?;
1097 } else if self.position == 1.0 {
1098 dest.write_str("to(")?;
1099 self.color.to_css(dest)?;
1100 } else {
1101 dest.write_str("color-stop(")?;
1102 self.position.to_css(dest)?;
1103 dest.delim(',', false)?;
1104 self.color.to_css(dest)?;
1105 }
1106 dest.write_char(')')
1107 }
1108}
1109
1110impl WebKitColorStop {
1111 fn get_fallback(&self, kind: ColorFallbackKind) -> WebKitColorStop {
1112 WebKitColorStop {
1113 color: self.color.get_fallback(kind),
1114 position: self.position,
1115 }
1116 }
1117}
1118
1119#[derive(Debug, Clone, PartialEq)]
1121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1122pub struct WebKitGradientPoint {
1123 pub x: WebKitGradientPointComponent<HorizontalPositionKeyword>,
1125 pub y: WebKitGradientPointComponent<VerticalPositionKeyword>,
1127}
1128
1129impl<'i> Parse<'i> for WebKitGradientPoint {
1130 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1131 let x = WebKitGradientPointComponent::parse(input)?;
1132 let y = WebKitGradientPointComponent::parse(input)?;
1133 Ok(WebKitGradientPoint { x, y })
1134 }
1135}
1136
1137impl ToCss for WebKitGradientPoint {
1138 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1139 where
1140 W: std::fmt::Write,
1141 {
1142 self.x.to_css(dest)?;
1143 dest.write_char(' ')?;
1144 self.y.to_css(dest)
1145 }
1146}
1147
1148#[derive(Debug, Clone, PartialEq)]
1150#[cfg_attr(
1151 feature = "serde",
1152 derive(serde::Serialize, serde::Deserialize),
1153 serde(tag = "type", content = "value", rename_all = "kebab-case")
1154)]
1155pub enum WebKitGradientPointComponent<S> {
1156 Center,
1158 Number(NumberOrPercentage),
1160 Side(S),
1162}
1163
1164impl<'i, S: Parse<'i>> Parse<'i> for WebKitGradientPointComponent<S> {
1165 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1166 if input.try_parse(|i| i.expect_ident_matching("center")).is_ok() {
1167 return Ok(WebKitGradientPointComponent::Center);
1168 }
1169
1170 if let Ok(lp) = input.try_parse(NumberOrPercentage::parse) {
1171 return Ok(WebKitGradientPointComponent::Number(lp));
1172 }
1173
1174 let keyword = S::parse(input)?;
1175 Ok(WebKitGradientPointComponent::Side(keyword))
1176 }
1177}
1178
1179impl<S: ToCss + Clone + Into<LengthPercentage>> ToCss for WebKitGradientPointComponent<S> {
1180 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1181 where
1182 W: std::fmt::Write,
1183 {
1184 use WebKitGradientPointComponent::*;
1185 match &self {
1186 Center => {
1187 if dest.minify {
1188 dest.write_str("50%")
1189 } else {
1190 dest.write_str("center")
1191 }
1192 }
1193 Number(lp) => {
1194 if matches!(lp, NumberOrPercentage::Percentage(Percentage(p)) if *p == 0.0) {
1195 dest.write_char('0')
1196 } else {
1197 lp.to_css(dest)
1198 }
1199 }
1200 Side(s) => {
1201 if dest.minify {
1202 let lp: LengthPercentage = s.clone().into();
1203 lp.to_css(dest)?;
1204 } else {
1205 s.to_css(dest)?;
1206 }
1207 Ok(())
1208 }
1209 }
1210 }
1211}
1212
1213impl<S: Clone> WebKitGradientPointComponent<S> {
1214 fn from_position(pos: &PositionComponent<S>) -> Result<WebKitGradientPointComponent<S>, ()> {
1216 match pos {
1217 PositionComponent::Center => Ok(WebKitGradientPointComponent::Center),
1218 PositionComponent::Length(len) => {
1219 Ok(WebKitGradientPointComponent::Number(match len {
1220 LengthPercentage::Percentage(p) => NumberOrPercentage::Percentage(p.clone()),
1221 LengthPercentage::Dimension(d) => {
1222 if let Some(px) = d.to_px() {
1224 NumberOrPercentage::Number(px)
1225 } else {
1226 return Err(());
1227 }
1228 }
1229 _ => return Err(()),
1230 }))
1231 }
1232 PositionComponent::Side(s, o) => {
1233 if o.is_some() {
1234 return Err(());
1235 }
1236 Ok(WebKitGradientPointComponent::Side(s.clone()))
1237 }
1238 }
1239 }
1240}
1241
1242impl WebKitGradient {
1243 pub fn from_standard(gradient: &Gradient) -> Result<WebKitGradient, ()> {
1245 match gradient {
1246 Gradient::Linear(linear, _) => {
1247 let (from, to) = match &linear.direction {
1249 LineDirection::Horizontal(horizontal) => match horizontal {
1250 HorizontalPositionKeyword::Left => ((1.0, 0.0), (0.0, 0.0)),
1251 HorizontalPositionKeyword::Right => ((0.0, 0.0), (1.0, 0.0)),
1252 },
1253 LineDirection::Vertical(vertical) => match vertical {
1254 VerticalPositionKeyword::Top => ((0.0, 1.0), (0.0, 0.0)),
1255 VerticalPositionKeyword::Bottom => ((0.0, 0.0), (0.0, 1.0)),
1256 },
1257 LineDirection::Corner(horizontal, vertical) => match (horizontal, vertical) {
1258 (HorizontalPositionKeyword::Left, VerticalPositionKeyword::Top) => ((1.0, 1.0), (0.0, 0.0)),
1259 (HorizontalPositionKeyword::Left, VerticalPositionKeyword::Bottom) => ((1.0, 0.0), (0.0, 1.0)),
1260 (HorizontalPositionKeyword::Right, VerticalPositionKeyword::Top) => ((0.0, 1.0), (1.0, 0.0)),
1261 (HorizontalPositionKeyword::Right, VerticalPositionKeyword::Bottom) => ((0.0, 0.0), (1.0, 1.0)),
1262 },
1263 LineDirection::Angle(angle) => {
1264 let degrees = angle.to_degrees();
1265 if degrees == 0.0 {
1266 ((0.0, 1.0), (0.0, 0.0))
1267 } else if degrees == 90.0 {
1268 ((0.0, 0.0), (1.0, 0.0))
1269 } else if degrees == 180.0 {
1270 ((0.0, 0.0), (0.0, 1.0))
1271 } else if degrees == 270.0 {
1272 ((1.0, 0.0), (0.0, 0.0))
1273 } else {
1274 return Err(());
1275 }
1276 }
1277 };
1278
1279 Ok(WebKitGradient::Linear {
1280 from: WebKitGradientPoint {
1281 x: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(from.0))),
1282 y: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(from.1))),
1283 },
1284 to: WebKitGradientPoint {
1285 x: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(to.0))),
1286 y: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(to.1))),
1287 },
1288 stops: convert_stops_to_webkit(&linear.items)?,
1289 })
1290 }
1291 Gradient::Radial(radial, _) => {
1292 let radius = match &radial.shape {
1294 EndingShape::Circle(Circle::Radius(radius)) => {
1295 if let Some(r) = radius.to_px() {
1296 r
1297 } else {
1298 return Err(());
1299 }
1300 }
1301 _ => return Err(()),
1302 };
1303
1304 let x = WebKitGradientPointComponent::from_position(&radial.position.x)?;
1305 let y = WebKitGradientPointComponent::from_position(&radial.position.y)?;
1306 let point = WebKitGradientPoint { x, y };
1307 Ok(WebKitGradient::Radial {
1308 from: point.clone(),
1309 r0: 0.0,
1310 to: point,
1311 r1: radius,
1312 stops: convert_stops_to_webkit(&radial.items)?,
1313 })
1314 }
1315 _ => Err(()),
1316 }
1317 }
1318}
1319
1320fn convert_stops_to_webkit(items: &Vec<GradientItem<LengthPercentage>>) -> Result<Vec<WebKitColorStop>, ()> {
1321 let mut stops = Vec::with_capacity(items.len());
1322 for (i, item) in items.iter().enumerate() {
1323 match item {
1324 GradientItem::ColorStop(stop) => {
1325 let position = if let Some(pos) = &stop.position {
1327 if let LengthPercentage::Percentage(position) = pos {
1328 position.0
1329 } else {
1330 return Err(());
1331 }
1332 } else if i == 0 {
1333 0.0
1334 } else if i == items.len() - 1 {
1335 1.0
1336 } else {
1337 return Err(());
1338 };
1339
1340 stops.push(WebKitColorStop {
1341 color: stop.color.clone(),
1342 position,
1343 })
1344 }
1345 _ => return Err(()),
1346 }
1347 }
1348
1349 Ok(stops)
1350}