use super::angle::{Angle, AnglePercentage};
use super::color::{ColorFallbackKind, CssColor};
use super::length::{Length, LengthPercentage};
use super::number::CSSNumber;
use super::percentage::{DimensionPercentage, NumberOrPercentage, Percentage};
use super::position::{HorizontalPositionKeyword, VerticalPositionKeyword};
use super::position::{Position, PositionComponent};
use crate::compat;
use crate::error::{ParserError, PrinterError};
use crate::macros::enum_property;
use crate::prefixes::Feature;
use crate::printer::Printer;
use crate::targets::{should_compile, Browsers, Targets};
use crate::traits::{IsCompatible, Parse, ToCss, TrySign, Zero};
use crate::vendor_prefix::VendorPrefix;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;
#[cfg(feature = "serde")]
use crate::serialization::ValueWrapper;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum Gradient {
Linear(LinearGradient),
RepeatingLinear(LinearGradient),
Radial(RadialGradient),
RepeatingRadial(RadialGradient),
Conic(ConicGradient),
RepeatingConic(ConicGradient),
#[cfg_attr(feature = "serde", serde(rename = "webkit-gradient"))]
WebKitGradient(WebKitGradient),
}
impl Gradient {
pub fn get_vendor_prefix(&self) -> VendorPrefix {
match self {
Gradient::Linear(LinearGradient { vendor_prefix, .. })
| Gradient::RepeatingLinear(LinearGradient { vendor_prefix, .. })
| Gradient::Radial(RadialGradient { vendor_prefix, .. })
| Gradient::RepeatingRadial(RadialGradient { vendor_prefix, .. }) => *vendor_prefix,
Gradient::WebKitGradient(_) => VendorPrefix::WebKit,
_ => VendorPrefix::None,
}
}
pub fn get_necessary_prefixes(&self, targets: Targets) -> VendorPrefix {
macro_rules! get_prefixes {
($feature: ident, $prefix: expr) => {
targets.prefixes($prefix, Feature::$feature)
};
}
match self {
Gradient::Linear(linear) => get_prefixes!(LinearGradient, linear.vendor_prefix),
Gradient::RepeatingLinear(linear) => get_prefixes!(RepeatingLinearGradient, linear.vendor_prefix),
Gradient::Radial(radial) => get_prefixes!(RadialGradient, radial.vendor_prefix),
Gradient::RepeatingRadial(radial) => get_prefixes!(RepeatingRadialGradient, radial.vendor_prefix),
_ => VendorPrefix::None,
}
}
pub fn get_prefixed(&self, prefix: VendorPrefix) -> Gradient {
match self {
Gradient::Linear(linear) => Gradient::Linear(LinearGradient {
vendor_prefix: prefix,
..linear.clone()
}),
Gradient::RepeatingLinear(linear) => Gradient::RepeatingLinear(LinearGradient {
vendor_prefix: prefix,
..linear.clone()
}),
Gradient::Radial(radial) => Gradient::Radial(RadialGradient {
vendor_prefix: prefix,
..radial.clone()
}),
Gradient::RepeatingRadial(radial) => Gradient::RepeatingRadial(RadialGradient {
vendor_prefix: prefix,
..radial.clone()
}),
_ => self.clone(),
}
}
pub fn get_legacy_webkit(&self) -> Result<Gradient, ()> {
Ok(Gradient::WebKitGradient(WebKitGradient::from_standard(self)?))
}
pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
match self {
Gradient::Linear(LinearGradient { items, .. })
| Gradient::Radial(RadialGradient { items, .. })
| Gradient::RepeatingLinear(LinearGradient { items, .. })
| Gradient::RepeatingRadial(RadialGradient { items, .. }) => {
let mut fallbacks = ColorFallbackKind::empty();
for item in items {
fallbacks |= item.get_necessary_fallbacks(targets)
}
fallbacks
}
Gradient::Conic(ConicGradient { items, .. }) | Gradient::RepeatingConic(ConicGradient { items, .. }) => {
let mut fallbacks = ColorFallbackKind::empty();
for item in items {
fallbacks |= item.get_necessary_fallbacks(targets)
}
fallbacks
}
Gradient::WebKitGradient(..) => ColorFallbackKind::empty(),
}
}
pub fn get_fallback(&self, kind: ColorFallbackKind) -> Gradient {
match self {
Gradient::Linear(g) => Gradient::Linear(g.get_fallback(kind)),
Gradient::RepeatingLinear(g) => Gradient::RepeatingLinear(g.get_fallback(kind)),
Gradient::Radial(g) => Gradient::Radial(g.get_fallback(kind)),
Gradient::RepeatingRadial(g) => Gradient::RepeatingRadial(g.get_fallback(kind)),
Gradient::Conic(g) => Gradient::Conic(g.get_fallback(kind)),
Gradient::RepeatingConic(g) => Gradient::RepeatingConic(g.get_fallback(kind)),
Gradient::WebKitGradient(g) => Gradient::WebKitGradient(g.get_fallback(kind)),
}
}
}
impl<'i> Parse<'i> for Gradient {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let location = input.current_source_location();
let func = input.expect_function()?.clone();
input.parse_nested_block(|input| {
match_ignore_ascii_case! { &func,
"linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::None)?)),
"repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::None)?)),
"radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::None)?)),
"repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::None)?)),
"conic-gradient" => Ok(Gradient::Conic(ConicGradient::parse(input)?)),
"repeating-conic-gradient" => Ok(Gradient::RepeatingConic(ConicGradient::parse(input)?)),
"-webkit-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::WebKit)?)),
"-webkit-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::WebKit)?)),
"-webkit-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::WebKit)?)),
"-webkit-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::WebKit)?)),
"-moz-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::Moz)?)),
"-moz-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::Moz)?)),
"-moz-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::Moz)?)),
"-moz-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::Moz)?)),
"-o-linear-gradient" => Ok(Gradient::Linear(LinearGradient::parse(input, VendorPrefix::O)?)),
"-o-repeating-linear-gradient" => Ok(Gradient::RepeatingLinear(LinearGradient::parse(input, VendorPrefix::O)?)),
"-o-radial-gradient" => Ok(Gradient::Radial(RadialGradient::parse(input, VendorPrefix::O)?)),
"-o-repeating-radial-gradient" => Ok(Gradient::RepeatingRadial(RadialGradient::parse(input, VendorPrefix::O)?)),
"-webkit-gradient" => Ok(Gradient::WebKitGradient(WebKitGradient::parse(input)?)),
_ => Err(location.new_unexpected_token_error(cssparser::Token::Ident(func.clone())))
}
})
}
}
impl ToCss for Gradient {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
let (f, prefix) = match self {
Gradient::Linear(g) => ("linear-gradient(", Some(g.vendor_prefix)),
Gradient::RepeatingLinear(g) => ("repeating-linear-gradient(", Some(g.vendor_prefix)),
Gradient::Radial(g) => ("radial-gradient(", Some(g.vendor_prefix)),
Gradient::RepeatingRadial(g) => ("repeating-radial-gradient(", Some(g.vendor_prefix)),
Gradient::Conic(_) => ("conic-gradient(", None),
Gradient::RepeatingConic(_) => ("repeating-conic-gradient(", None),
Gradient::WebKitGradient(_) => ("-webkit-gradient(", None),
};
if let Some(prefix) = prefix {
prefix.to_css(dest)?;
}
dest.write_str(f)?;
match self {
Gradient::Linear(linear) | Gradient::RepeatingLinear(linear) => {
linear.to_css(dest, linear.vendor_prefix != VendorPrefix::None)?
}
Gradient::Radial(radial) | Gradient::RepeatingRadial(radial) => radial.to_css(dest)?,
Gradient::Conic(conic) | Gradient::RepeatingConic(conic) => conic.to_css(dest)?,
Gradient::WebKitGradient(g) => g.to_css(dest)?,
}
dest.write_char(')')
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct LinearGradient {
pub vendor_prefix: VendorPrefix,
pub direction: LineDirection,
pub items: Vec<GradientItem<LengthPercentage>>,
}
impl LinearGradient {
fn parse<'i, 't>(
input: &mut Parser<'i, 't>,
vendor_prefix: VendorPrefix,
) -> Result<LinearGradient, ParseError<'i, ParserError<'i>>> {
let direction = if let Ok(direction) =
input.try_parse(|input| LineDirection::parse(input, vendor_prefix != VendorPrefix::None))
{
input.expect_comma()?;
direction
} else {
LineDirection::Vertical(VerticalPositionKeyword::Bottom)
};
let items = parse_items(input)?;
Ok(LinearGradient {
direction,
items,
vendor_prefix,
})
}
fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
let angle = match &self.direction {
LineDirection::Vertical(VerticalPositionKeyword::Bottom) => 180.0,
LineDirection::Vertical(VerticalPositionKeyword::Top) => 0.0,
LineDirection::Angle(angle) => angle.to_degrees(),
_ => -1.0,
};
if angle == 180.0 {
serialize_items(&self.items, dest)
} else if angle == 0.0
&& dest.minify
&& self.items.iter().all(|item| {
matches!(
item,
GradientItem::Hint(LengthPercentage::Percentage(_))
| GradientItem::ColorStop(ColorStop {
position: None | Some(LengthPercentage::Percentage(_)),
..
})
)
})
{
let items: Vec<GradientItem<LengthPercentage>> = self
.items
.iter()
.rev()
.map(|item| {
match item {
GradientItem::Hint(LengthPercentage::Percentage(p)) => {
GradientItem::Hint(LengthPercentage::Percentage(Percentage(1.0 - p.0)))
}
GradientItem::ColorStop(ColorStop { color, position }) => GradientItem::ColorStop(ColorStop {
color: color.clone(),
position: position.clone().map(|p| match p {
LengthPercentage::Percentage(p) => LengthPercentage::Percentage(Percentage(1.0 - p.0)),
_ => unreachable!(),
}),
}),
_ => unreachable!(),
}
})
.collect();
serialize_items(&items, dest)
} else {
if self.direction != LineDirection::Vertical(VerticalPositionKeyword::Bottom)
&& self.direction != LineDirection::Angle(Angle::Deg(180.0))
{
self.direction.to_css(dest, is_prefixed)?;
dest.delim(',', false)?;
}
serialize_items(&self.items, dest)
}
}
fn get_fallback(&self, kind: ColorFallbackKind) -> LinearGradient {
LinearGradient {
direction: self.direction.clone(),
items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
vendor_prefix: self.vendor_prefix,
}
}
}
impl IsCompatible for LinearGradient {
fn is_compatible(&self, browsers: Browsers) -> bool {
self.items.iter().all(|item| item.is_compatible(browsers))
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct RadialGradient {
pub vendor_prefix: VendorPrefix,
pub shape: EndingShape,
pub position: Position,
pub items: Vec<GradientItem<LengthPercentage>>,
}
impl<'i> RadialGradient {
fn parse<'t>(
input: &mut Parser<'i, 't>,
vendor_prefix: VendorPrefix,
) -> Result<RadialGradient, ParseError<'i, ParserError<'i>>> {
let shape = input.try_parse(EndingShape::parse).ok();
let position = input
.try_parse(|input| {
input.expect_ident_matching("at")?;
Position::parse(input)
})
.ok();
if shape.is_some() || position.is_some() {
input.expect_comma()?;
}
let items = parse_items(input)?;
Ok(RadialGradient {
shape: shape.unwrap_or_default(),
position: position.unwrap_or(Position::center()),
items,
vendor_prefix,
})
}
}
impl ToCss for RadialGradient {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if self.shape != EndingShape::default() {
self.shape.to_css(dest)?;
if self.position.is_center() {
dest.delim(',', false)?;
} else {
dest.write_char(' ')?;
}
}
if !self.position.is_center() {
dest.write_str("at ")?;
self.position.to_css(dest)?;
dest.delim(',', false)?;
}
serialize_items(&self.items, dest)
}
}
impl RadialGradient {
fn get_fallback(&self, kind: ColorFallbackKind) -> RadialGradient {
RadialGradient {
shape: self.shape.clone(),
position: self.position.clone(),
items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
vendor_prefix: self.vendor_prefix,
}
}
}
impl IsCompatible for RadialGradient {
fn is_compatible(&self, browsers: Browsers) -> bool {
self.items.iter().all(|item| item.is_compatible(browsers))
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum LineDirection {
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<Angle>"))]
Angle(Angle),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<HorizontalPositionKeyword>"))]
Horizontal(HorizontalPositionKeyword),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<VerticalPositionKeyword>"))]
Vertical(VerticalPositionKeyword),
Corner {
horizontal: HorizontalPositionKeyword,
vertical: VerticalPositionKeyword,
},
}
impl LineDirection {
fn parse<'i, 't>(
input: &mut Parser<'i, 't>,
is_prefixed: bool,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if let Ok(angle) = input.try_parse(Angle::parse_with_unitless_zero) {
return Ok(LineDirection::Angle(angle));
}
if !is_prefixed {
input.expect_ident_matching("to")?;
}
if let Ok(x) = input.try_parse(HorizontalPositionKeyword::parse) {
if let Ok(y) = input.try_parse(VerticalPositionKeyword::parse) {
return Ok(LineDirection::Corner {
horizontal: x,
vertical: y,
});
}
return Ok(LineDirection::Horizontal(x));
}
let y = VerticalPositionKeyword::parse(input)?;
if let Ok(x) = input.try_parse(HorizontalPositionKeyword::parse) {
return Ok(LineDirection::Corner {
horizontal: x,
vertical: y,
});
}
Ok(LineDirection::Vertical(y))
}
fn to_css<W>(&self, dest: &mut Printer<W>, is_prefixed: bool) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
LineDirection::Angle(angle) => angle.to_css(dest),
LineDirection::Horizontal(k) => {
if dest.minify {
match k {
HorizontalPositionKeyword::Left => dest.write_str("270deg"),
HorizontalPositionKeyword::Right => dest.write_str("90deg"),
}
} else {
if !is_prefixed {
dest.write_str("to ")?;
}
k.to_css(dest)
}
}
LineDirection::Vertical(k) => {
if dest.minify {
match k {
VerticalPositionKeyword::Top => dest.write_str("0deg"),
VerticalPositionKeyword::Bottom => dest.write_str("180deg"),
}
} else {
if !is_prefixed {
dest.write_str("to ")?;
}
k.to_css(dest)
}
}
LineDirection::Corner { horizontal, vertical } => {
if !is_prefixed {
dest.write_str("to ")?;
}
vertical.to_css(dest)?;
dest.write_char(' ')?;
horizontal.to_css(dest)
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum EndingShape {
Circle(Circle),
Ellipse(Ellipse),
}
impl Default for EndingShape {
fn default() -> EndingShape {
EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
}
}
impl<'i> Parse<'i> for EndingShape {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if let Ok(ellipse) = input.try_parse(Ellipse::parse) {
return Ok(EndingShape::Ellipse(ellipse));
}
if let Ok(circle) = input.try_parse(Circle::parse) {
return Ok(EndingShape::Circle(circle));
}
return Err(input.new_error_for_next_token());
}
}
impl ToCss for EndingShape {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
EndingShape::Circle(circle) => circle.to_css(dest),
EndingShape::Ellipse(ellipse) => ellipse.to_css(dest),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum Circle {
Radius(Length),
Extent(ShapeExtent),
}
impl<'i> Parse<'i> for Circle {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
input.expect_ident_matching("circle")?;
return Ok(Circle::Extent(extent));
}
if let Ok(length) = input.try_parse(Length::parse) {
let _ = input.try_parse(|input| input.expect_ident_matching("circle"));
return Ok(Circle::Radius(length));
}
if input.try_parse(|input| input.expect_ident_matching("circle")).is_ok() {
if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
return Ok(Circle::Extent(extent));
}
if let Ok(length) = input.try_parse(Length::parse) {
return Ok(Circle::Radius(length));
}
return Ok(Circle::Extent(ShapeExtent::FarthestCorner));
}
return Err(input.new_error_for_next_token());
}
}
impl ToCss for Circle {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Circle::Radius(r) => r.to_css(dest),
Circle::Extent(extent) => {
dest.write_str("circle")?;
if *extent != ShapeExtent::FarthestCorner {
dest.write_char(' ')?;
extent.to_css(dest)?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum Ellipse {
Size {
x: LengthPercentage,
y: LengthPercentage,
},
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<ShapeExtent>"))]
Extent(ShapeExtent),
}
impl<'i> Parse<'i> for Ellipse {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
if input.try_parse(|input| input.expect_ident_matching("circle")).is_ok() {
return Err(input.new_error_for_next_token());
}
let _ = input.try_parse(|input| input.expect_ident_matching("ellipse"));
return Ok(Ellipse::Extent(extent));
}
if let Ok(x) = input.try_parse(LengthPercentage::parse) {
let y = LengthPercentage::parse(input)?;
let _ = input.try_parse(|input| input.expect_ident_matching("ellipse"));
return Ok(Ellipse::Size { x, y });
}
if input.try_parse(|input| input.expect_ident_matching("ellipse")).is_ok() {
if let Ok(extent) = input.try_parse(ShapeExtent::parse) {
return Ok(Ellipse::Extent(extent));
}
if let Ok(x) = input.try_parse(LengthPercentage::parse) {
let y = LengthPercentage::parse(input)?;
return Ok(Ellipse::Size { x, y });
}
return Ok(Ellipse::Extent(ShapeExtent::FarthestCorner));
}
return Err(input.new_error_for_next_token());
}
}
impl ToCss for Ellipse {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Ellipse::Size { x, y } => {
x.to_css(dest)?;
dest.write_char(' ')?;
y.to_css(dest)
}
Ellipse::Extent(extent) => extent.to_css(dest),
}
}
}
enum_property! {
pub enum ShapeExtent {
"closest-side": ClosestSide,
"farthest-side": FarthestSide,
"closest-corner": ClosestCorner,
"farthest-corner": FarthestCorner,
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct ConicGradient {
pub angle: Angle,
pub position: Position,
pub items: Vec<GradientItem<AnglePercentage>>,
}
impl ConicGradient {
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let angle = input.try_parse(|input| {
input.expect_ident_matching("from")?;
Angle::parse_with_unitless_zero(input)
});
let position = input.try_parse(|input| {
input.expect_ident_matching("at")?;
Position::parse(input)
});
if angle.is_ok() || position.is_ok() {
input.expect_comma()?;
}
let items = parse_items(input)?;
Ok(ConicGradient {
angle: angle.unwrap_or(Angle::Deg(0.0)),
position: position.unwrap_or(Position::center()),
items,
})
}
}
impl ToCss for ConicGradient {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if !self.angle.is_zero() {
dest.write_str("from ")?;
self.angle.to_css(dest)?;
if self.position.is_center() {
dest.delim(',', false)?;
} else {
dest.write_char(' ')?;
}
}
if !self.position.is_center() {
dest.write_str("at ")?;
self.position.to_css(dest)?;
dest.delim(',', false)?;
}
serialize_items(&self.items, dest)
}
}
impl ConicGradient {
fn get_fallback(&self, kind: ColorFallbackKind) -> ConicGradient {
ConicGradient {
angle: self.angle.clone(),
position: self.position.clone(),
items: self.items.iter().map(|item| item.get_fallback(kind)).collect(),
}
}
}
impl IsCompatible for ConicGradient {
fn is_compatible(&self, browsers: Browsers) -> bool {
self.items.iter().all(|item| item.is_compatible(browsers))
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct ColorStop<D> {
pub color: CssColor,
pub position: Option<D>,
}
impl<'i, D: Parse<'i>> Parse<'i> for ColorStop<D> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let color = CssColor::parse(input)?;
let position = input.try_parse(D::parse).ok();
Ok(ColorStop { color, position })
}
}
impl<D: ToCss> ToCss for ColorStop<D> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.color.to_css(dest)?;
if let Some(position) = &self.position {
dest.write_char(' ')?;
position.to_css(dest)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum GradientItem<D> {
ColorStop(ColorStop<D>),
#[cfg_attr(
feature = "serde",
serde(
bound(serialize = "D: serde::Serialize", deserialize = "D: serde::Deserialize<'de>"),
with = "ValueWrapper::<D>"
)
)]
Hint(D),
}
impl<D: ToCss> ToCss for GradientItem<D> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
GradientItem::ColorStop(stop) => stop.to_css(dest),
GradientItem::Hint(hint) => hint.to_css(dest),
}
}
}
impl<D: Clone> GradientItem<D> {
pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
match self {
GradientItem::ColorStop(stop) => stop.color.get_necessary_fallbacks(targets),
GradientItem::Hint(..) => ColorFallbackKind::empty(),
}
}
pub fn get_fallback(&self, kind: ColorFallbackKind) -> GradientItem<D> {
match self {
GradientItem::ColorStop(stop) => GradientItem::ColorStop(ColorStop {
color: stop.color.get_fallback(kind),
position: stop.position.clone(),
}),
GradientItem::Hint(..) => self.clone(),
}
}
}
impl<D> IsCompatible for GradientItem<D> {
fn is_compatible(&self, browsers: Browsers) -> bool {
match self {
GradientItem::ColorStop(c) => c.color.is_compatible(browsers),
GradientItem::Hint(..) => compat::Feature::GradientInterpolationHints.is_compatible(browsers),
}
}
}
fn parse_items<'i, 't, D: Parse<'i>>(
input: &mut Parser<'i, 't>,
) -> Result<Vec<GradientItem<D>>, ParseError<'i, ParserError<'i>>> {
let mut items = Vec::new();
let mut seen_stop = false;
loop {
input.parse_until_before(Delimiter::Comma, |input| {
if seen_stop {
if let Ok(hint) = input.try_parse(D::parse) {
seen_stop = false;
items.push(GradientItem::Hint(hint));
return Ok(());
}
}
let stop = ColorStop::parse(input)?;
if let Ok(position) = input.try_parse(D::parse) {
let color = stop.color.clone();
items.push(GradientItem::ColorStop(stop));
items.push(GradientItem::ColorStop(ColorStop {
color,
position: Some(position),
}))
} else {
items.push(GradientItem::ColorStop(stop));
}
seen_stop = true;
Ok(())
})?;
match input.next() {
Err(_) => break,
Ok(Token::Comma) => continue,
_ => unreachable!(),
}
}
Ok(items)
}
fn serialize_items<
D: ToCss + std::cmp::PartialEq<D> + std::ops::Mul<f32, Output = D> + TrySign + Clone + std::fmt::Debug,
W,
>(
items: &Vec<GradientItem<DimensionPercentage<D>>>,
dest: &mut Printer<W>,
) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
let mut first = true;
let mut last: Option<&GradientItem<DimensionPercentage<D>>> = None;
for item in items {
if *item == GradientItem::Hint(DimensionPercentage::Percentage(Percentage(0.5))) {
continue;
}
if let Some(prev) = last {
if !should_compile!(dest.targets, DoublePositionGradients) {
match (prev, item) {
(
GradientItem::ColorStop(ColorStop {
position: Some(_),
color: ca,
}),
GradientItem::ColorStop(ColorStop {
position: Some(p),
color: cb,
}),
) if ca == cb => {
dest.write_char(' ')?;
p.to_css(dest)?;
last = None;
continue;
}
_ => {}
}
}
}
if first {
first = false;
} else {
dest.delim(',', false)?;
}
item.to_css(dest)?;
last = Some(item)
}
Ok(())
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "kind", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum WebKitGradient {
Linear {
from: WebKitGradientPoint,
to: WebKitGradientPoint,
stops: Vec<WebKitColorStop>,
},
Radial {
from: WebKitGradientPoint,
r0: CSSNumber,
to: WebKitGradientPoint,
r1: CSSNumber,
stops: Vec<WebKitColorStop>,
},
}
impl<'i> Parse<'i> for WebKitGradient {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let location = input.current_source_location();
let ident = input.expect_ident_cloned()?;
input.expect_comma()?;
match_ignore_ascii_case! { &ident,
"linear" => {
let from = WebKitGradientPoint::parse(input)?;
input.expect_comma()?;
let to = WebKitGradientPoint::parse(input)?;
input.expect_comma()?;
let stops = input.parse_comma_separated(WebKitColorStop::parse)?;
Ok(WebKitGradient::Linear {
from,
to,
stops
})
},
"radial" => {
let from = WebKitGradientPoint::parse(input)?;
input.expect_comma()?;
let r0 = CSSNumber::parse(input)?;
input.expect_comma()?;
let to = WebKitGradientPoint::parse(input)?;
input.expect_comma()?;
let r1 = CSSNumber::parse(input)?;
input.expect_comma()?;
let stops = input.parse_comma_separated(WebKitColorStop::parse)?;
Ok(WebKitGradient::Radial {
from,
r0,
to,
r1,
stops
})
},
_ => Err(location.new_unexpected_token_error(cssparser::Token::Ident(ident.clone())))
}
}
}
impl ToCss for WebKitGradient {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
WebKitGradient::Linear { from, to, stops } => {
dest.write_str("linear")?;
dest.delim(',', false)?;
from.to_css(dest)?;
dest.delim(',', false)?;
to.to_css(dest)?;
for stop in stops {
dest.delim(',', false)?;
stop.to_css(dest)?;
}
Ok(())
}
WebKitGradient::Radial {
from,
r0,
to,
r1,
stops,
} => {
dest.write_str("radial")?;
dest.delim(',', false)?;
from.to_css(dest)?;
dest.delim(',', false)?;
r0.to_css(dest)?;
dest.delim(',', false)?;
to.to_css(dest)?;
dest.delim(',', false)?;
r1.to_css(dest)?;
for stop in stops {
dest.delim(',', false)?;
stop.to_css(dest)?;
}
Ok(())
}
}
}
}
impl WebKitGradient {
fn get_fallback(&self, kind: ColorFallbackKind) -> WebKitGradient {
let stops = match self {
WebKitGradient::Linear { stops, .. } => stops,
WebKitGradient::Radial { stops, .. } => stops,
};
let stops = stops.iter().map(|stop| stop.get_fallback(kind)).collect();
match self {
WebKitGradient::Linear { from, to, .. } => WebKitGradient::Linear {
from: from.clone(),
to: to.clone(),
stops,
},
WebKitGradient::Radial { from, r0, to, r1, .. } => WebKitGradient::Radial {
from: from.clone(),
r0: *r0,
to: to.clone(),
r1: *r1,
stops,
},
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct WebKitColorStop {
pub color: CssColor,
pub position: CSSNumber,
}
impl<'i> Parse<'i> for WebKitColorStop {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let location = input.current_source_location();
let function = input.expect_function()?.clone();
input.parse_nested_block(|input| {
let position = match_ignore_ascii_case! { &function,
"color-stop" => {
let p = NumberOrPercentage::parse(input)?;
input.expect_comma()?;
(&p).into()
},
"from" => 0.0,
"to" => 1.0,
_ => return Err(location.new_unexpected_token_error(cssparser::Token::Ident(function.clone())))
};
let color = CssColor::parse(input)?;
Ok(WebKitColorStop { color, position })
})
}
}
impl ToCss for WebKitColorStop {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if self.position == 0.0 {
dest.write_str("from(")?;
self.color.to_css(dest)?;
} else if self.position == 1.0 {
dest.write_str("to(")?;
self.color.to_css(dest)?;
} else {
dest.write_str("color-stop(")?;
self.position.to_css(dest)?;
dest.delim(',', false)?;
self.color.to_css(dest)?;
}
dest.write_char(')')
}
}
impl WebKitColorStop {
fn get_fallback(&self, kind: ColorFallbackKind) -> WebKitColorStop {
WebKitColorStop {
color: self.color.get_fallback(kind),
position: self.position,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct WebKitGradientPoint {
pub x: WebKitGradientPointComponent<HorizontalPositionKeyword>,
pub y: WebKitGradientPointComponent<VerticalPositionKeyword>,
}
impl<'i> Parse<'i> for WebKitGradientPoint {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let x = WebKitGradientPointComponent::parse(input)?;
let y = WebKitGradientPointComponent::parse(input)?;
Ok(WebKitGradientPoint { x, y })
}
}
impl ToCss for WebKitGradientPoint {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.x.to_css(dest)?;
dest.write_char(' ')?;
self.y.to_css(dest)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum WebKitGradientPointComponent<S> {
Center,
Number(NumberOrPercentage),
Side(S),
}
impl<'i, S: Parse<'i>> Parse<'i> for WebKitGradientPointComponent<S> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if input.try_parse(|i| i.expect_ident_matching("center")).is_ok() {
return Ok(WebKitGradientPointComponent::Center);
}
if let Ok(lp) = input.try_parse(NumberOrPercentage::parse) {
return Ok(WebKitGradientPointComponent::Number(lp));
}
let keyword = S::parse(input)?;
Ok(WebKitGradientPointComponent::Side(keyword))
}
}
impl<S: ToCss + Clone + Into<LengthPercentage>> ToCss for WebKitGradientPointComponent<S> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
use WebKitGradientPointComponent::*;
match &self {
Center => {
if dest.minify {
dest.write_str("50%")
} else {
dest.write_str("center")
}
}
Number(lp) => {
if matches!(lp, NumberOrPercentage::Percentage(Percentage(p)) if *p == 0.0) {
dest.write_char('0')
} else {
lp.to_css(dest)
}
}
Side(s) => {
if dest.minify {
let lp: LengthPercentage = s.clone().into();
lp.to_css(dest)?;
} else {
s.to_css(dest)?;
}
Ok(())
}
}
}
}
impl<S: Clone> WebKitGradientPointComponent<S> {
fn from_position(pos: &PositionComponent<S>) -> Result<WebKitGradientPointComponent<S>, ()> {
match pos {
PositionComponent::Center => Ok(WebKitGradientPointComponent::Center),
PositionComponent::Length(len) => {
Ok(WebKitGradientPointComponent::Number(match len {
LengthPercentage::Percentage(p) => NumberOrPercentage::Percentage(p.clone()),
LengthPercentage::Dimension(d) => {
if let Some(px) = d.to_px() {
NumberOrPercentage::Number(px)
} else {
return Err(());
}
}
_ => return Err(()),
}))
}
PositionComponent::Side { side, offset } => {
if offset.is_some() {
return Err(());
}
Ok(WebKitGradientPointComponent::Side(side.clone()))
}
}
}
}
impl WebKitGradient {
pub fn from_standard(gradient: &Gradient) -> Result<WebKitGradient, ()> {
match gradient {
Gradient::Linear(linear) => {
let (from, to) = match &linear.direction {
LineDirection::Horizontal(horizontal) => match horizontal {
HorizontalPositionKeyword::Left => ((1.0, 0.0), (0.0, 0.0)),
HorizontalPositionKeyword::Right => ((0.0, 0.0), (1.0, 0.0)),
},
LineDirection::Vertical(vertical) => match vertical {
VerticalPositionKeyword::Top => ((0.0, 1.0), (0.0, 0.0)),
VerticalPositionKeyword::Bottom => ((0.0, 0.0), (0.0, 1.0)),
},
LineDirection::Corner { horizontal, vertical } => match (horizontal, vertical) {
(HorizontalPositionKeyword::Left, VerticalPositionKeyword::Top) => ((1.0, 1.0), (0.0, 0.0)),
(HorizontalPositionKeyword::Left, VerticalPositionKeyword::Bottom) => ((1.0, 0.0), (0.0, 1.0)),
(HorizontalPositionKeyword::Right, VerticalPositionKeyword::Top) => ((0.0, 1.0), (1.0, 0.0)),
(HorizontalPositionKeyword::Right, VerticalPositionKeyword::Bottom) => ((0.0, 0.0), (1.0, 1.0)),
},
LineDirection::Angle(angle) => {
let degrees = angle.to_degrees();
if degrees == 0.0 {
((0.0, 1.0), (0.0, 0.0))
} else if degrees == 90.0 {
((0.0, 0.0), (1.0, 0.0))
} else if degrees == 180.0 {
((0.0, 0.0), (0.0, 1.0))
} else if degrees == 270.0 {
((1.0, 0.0), (0.0, 0.0))
} else {
return Err(());
}
}
};
Ok(WebKitGradient::Linear {
from: WebKitGradientPoint {
x: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(from.0))),
y: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(from.1))),
},
to: WebKitGradientPoint {
x: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(to.0))),
y: WebKitGradientPointComponent::Number(NumberOrPercentage::Percentage(Percentage(to.1))),
},
stops: convert_stops_to_webkit(&linear.items)?,
})
}
Gradient::Radial(radial) => {
let radius = match &radial.shape {
EndingShape::Circle(Circle::Radius(radius)) => {
if let Some(r) = radius.to_px() {
r
} else {
return Err(());
}
}
_ => return Err(()),
};
let x = WebKitGradientPointComponent::from_position(&radial.position.x)?;
let y = WebKitGradientPointComponent::from_position(&radial.position.y)?;
let point = WebKitGradientPoint { x, y };
Ok(WebKitGradient::Radial {
from: point.clone(),
r0: 0.0,
to: point,
r1: radius,
stops: convert_stops_to_webkit(&radial.items)?,
})
}
_ => Err(()),
}
}
}
fn convert_stops_to_webkit(items: &Vec<GradientItem<LengthPercentage>>) -> Result<Vec<WebKitColorStop>, ()> {
let mut stops = Vec::with_capacity(items.len());
for (i, item) in items.iter().enumerate() {
match item {
GradientItem::ColorStop(stop) => {
let position = if let Some(pos) = &stop.position {
if let LengthPercentage::Percentage(position) = pos {
position.0
} else {
return Err(());
}
} else if i == 0 {
0.0
} else if i == items.len() - 1 {
1.0
} else {
return Err(());
};
stops.push(WebKitColorStop {
color: stop.color.clone(),
position,
})
}
_ => return Err(()),
}
}
Ok(stops)
}