mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
style: Implement parsing for CSS conic-gradient syntax.
Differential Revision: https://phabricator.services.mozilla.com/D62148
This commit is contained in:
parent
31187b0180
commit
61712d1a03
3 changed files with 114 additions and 31 deletions
|
@ -13,7 +13,7 @@ use crate::values::computed::url::ComputedImageUrl;
|
||||||
use crate::values::computed::NumberOrPercentage;
|
use crate::values::computed::NumberOrPercentage;
|
||||||
use crate::values::computed::{Angle, Color, Context};
|
use crate::values::computed::{Angle, Color, Context};
|
||||||
use crate::values::computed::{
|
use crate::values::computed::{
|
||||||
LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, ToComputedValue,
|
AngleOrPercentage, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, ToComputedValue,
|
||||||
};
|
};
|
||||||
use crate::values::generics::image::{self as generic, GradientCompatMode};
|
use crate::values::generics::image::{self as generic, GradientCompatMode};
|
||||||
use crate::values::specified::image::LineDirection as SpecifiedLineDirection;
|
use crate::values::specified::image::LineDirection as SpecifiedLineDirection;
|
||||||
|
@ -34,6 +34,8 @@ pub type Gradient = generic::GenericGradient<
|
||||||
NonNegativeLength,
|
NonNegativeLength,
|
||||||
NonNegativeLengthPercentage,
|
NonNegativeLengthPercentage,
|
||||||
Position,
|
Position,
|
||||||
|
Angle,
|
||||||
|
AngleOrPercentage,
|
||||||
Color,
|
Color,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
use crate::custom_properties;
|
use crate::custom_properties;
|
||||||
use crate::values::serialize_atom_identifier;
|
use crate::values::serialize_atom_identifier;
|
||||||
use crate::Atom;
|
use crate::Atom;
|
||||||
|
use crate::Zero;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use style_traits::{CssWriter, ToCss};
|
use style_traits::{CssWriter, ToCss};
|
||||||
|
@ -57,6 +58,8 @@ pub enum GenericGradient<
|
||||||
NonNegativeLength,
|
NonNegativeLength,
|
||||||
NonNegativeLengthPercentage,
|
NonNegativeLengthPercentage,
|
||||||
Position,
|
Position,
|
||||||
|
Angle,
|
||||||
|
AngleOrPercentage,
|
||||||
Color,
|
Color,
|
||||||
> {
|
> {
|
||||||
/// A linear gradient.
|
/// A linear gradient.
|
||||||
|
@ -83,6 +86,17 @@ pub enum GenericGradient<
|
||||||
/// Compatibility mode.
|
/// Compatibility mode.
|
||||||
compat_mode: GradientCompatMode,
|
compat_mode: GradientCompatMode,
|
||||||
},
|
},
|
||||||
|
/// A conic gradient.
|
||||||
|
Conic {
|
||||||
|
/// Start angle of gradient
|
||||||
|
angle: Angle,
|
||||||
|
/// Center of gradient
|
||||||
|
position: Position,
|
||||||
|
/// The color stops and interpolation hints.
|
||||||
|
items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>,
|
||||||
|
/// True if this is a repeating gradient.
|
||||||
|
repeating: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::GenericGradient as Gradient;
|
pub use self::GenericGradient as Gradient;
|
||||||
|
@ -308,13 +322,15 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D, LP, NL, NLP, P, C> ToCss for Gradient<D, LP, NL, NLP, P, C>
|
impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C>
|
||||||
where
|
where
|
||||||
D: LineDirection,
|
D: LineDirection,
|
||||||
LP: ToCss,
|
LP: ToCss,
|
||||||
NL: ToCss,
|
NL: ToCss,
|
||||||
NLP: ToCss,
|
NLP: ToCss,
|
||||||
P: ToCss,
|
P: ToCss,
|
||||||
|
A: ToCss,
|
||||||
|
AoP: ToCss,
|
||||||
C: ToCss,
|
C: ToCss,
|
||||||
{
|
{
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
@ -324,6 +340,7 @@ where
|
||||||
let (compat_mode, repeating) = match *self {
|
let (compat_mode, repeating) = match *self {
|
||||||
Gradient::Linear { compat_mode, repeating, .. } => (compat_mode, repeating),
|
Gradient::Linear { compat_mode, repeating, .. } => (compat_mode, repeating),
|
||||||
Gradient::Radial { compat_mode, repeating, .. } => (compat_mode, repeating),
|
Gradient::Radial { compat_mode, repeating, .. } => (compat_mode, repeating),
|
||||||
|
Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating),
|
||||||
};
|
};
|
||||||
|
|
||||||
match compat_mode {
|
match compat_mode {
|
||||||
|
@ -336,17 +353,24 @@ where
|
||||||
dest.write_str("repeating-")?;
|
dest.write_str("repeating-")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (items, mut skip_comma) = match *self {
|
match *self {
|
||||||
Gradient::Linear { ref direction, compat_mode, ref items, .. } => {
|
Gradient::Linear { ref direction, ref items, compat_mode, .. } => {
|
||||||
dest.write_str("linear-gradient(")?;
|
dest.write_str("linear-gradient(")?;
|
||||||
if !direction.points_downwards(compat_mode) {
|
let mut skip_comma = if !direction.points_downwards(compat_mode) {
|
||||||
direction.to_css(dest, compat_mode)?;
|
direction.to_css(dest, compat_mode)?;
|
||||||
(items, false)
|
false
|
||||||
} else {
|
} else {
|
||||||
(items, true)
|
true
|
||||||
|
};
|
||||||
|
for item in &**items {
|
||||||
|
if !skip_comma {
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
}
|
||||||
|
skip_comma = false;
|
||||||
|
item.to_css(dest)?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Gradient::Radial { ref shape, ref position, compat_mode, ref items, .. } => {
|
Gradient::Radial { ref shape, ref position, ref items, compat_mode, .. } => {
|
||||||
dest.write_str("radial-gradient(")?;
|
dest.write_str("radial-gradient(")?;
|
||||||
let omit_shape = match *shape {
|
let omit_shape = match *shape {
|
||||||
EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) |
|
EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) |
|
||||||
|
@ -367,15 +391,25 @@ where
|
||||||
shape.to_css(dest)?;
|
shape.to_css(dest)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(items, false)
|
for item in &**items {
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
item.to_css(dest)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Gradient::Conic { ref angle, ref position, ref items, .. } => {
|
||||||
|
dest.write_str("conic-gradient(")?;
|
||||||
|
if !angle.is_zero() {
|
||||||
|
dest.write_str("from ")?;
|
||||||
|
angle.to_css(dest)?;
|
||||||
|
dest.write_str(" ")?;
|
||||||
|
}
|
||||||
|
dest.write_str("at ")?;
|
||||||
|
position.to_css(dest)?;
|
||||||
|
for item in &**items {
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
item.to_css(dest)?;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
|
||||||
for item in &**items {
|
|
||||||
if !skip_comma {
|
|
||||||
dest.write_str(", ")?;
|
|
||||||
}
|
|
||||||
skip_comma = false;
|
|
||||||
item.to_css(dest)?;
|
|
||||||
}
|
}
|
||||||
dest.write_str(")")
|
dest.write_str(")")
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPosi
|
||||||
use crate::values::specified::position::{Position, PositionComponent, Side};
|
use crate::values::specified::position::{Position, PositionComponent, Side};
|
||||||
use crate::values::specified::url::SpecifiedImageUrl;
|
use crate::values::specified::url::SpecifiedImageUrl;
|
||||||
use crate::values::specified::{
|
use crate::values::specified::{
|
||||||
Angle, Color, Length, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage,
|
Angle, AngleOrPercentage, Color, Length, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage,
|
||||||
};
|
};
|
||||||
use crate::values::specified::{Number, NumberOrPercentage, Percentage};
|
use crate::values::specified::{Number, NumberOrPercentage, Percentage};
|
||||||
use crate::Atom;
|
use crate::Atom;
|
||||||
|
@ -44,6 +44,8 @@ pub type Gradient = generic::Gradient<
|
||||||
NonNegativeLength,
|
NonNegativeLength,
|
||||||
NonNegativeLengthPercentage,
|
NonNegativeLengthPercentage,
|
||||||
Position,
|
Position,
|
||||||
|
Angle,
|
||||||
|
AngleOrPercentage,
|
||||||
Color,
|
Color,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
@ -69,6 +71,13 @@ impl SpecifiedValueInfo for Gradient {
|
||||||
"-moz-repeating-radial-gradient",
|
"-moz-repeating-radial-gradient",
|
||||||
"-webkit-gradient",
|
"-webkit-gradient",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if static_prefs::pref!("layout.css.conic-gradient.enabled") {
|
||||||
|
f(&[
|
||||||
|
"conic-gradient",
|
||||||
|
"repeating-conic-gradient",
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,6 +197,7 @@ impl Parse for Gradient {
|
||||||
enum Shape {
|
enum Shape {
|
||||||
Linear,
|
Linear,
|
||||||
Radial,
|
Radial,
|
||||||
|
Conic,
|
||||||
}
|
}
|
||||||
|
|
||||||
let func = input.expect_function()?;
|
let func = input.expect_function()?;
|
||||||
|
@ -232,6 +242,12 @@ impl Parse for Gradient {
|
||||||
"-moz-repeating-radial-gradient" => {
|
"-moz-repeating-radial-gradient" => {
|
||||||
(Shape::Radial, true, GradientCompatMode::Moz)
|
(Shape::Radial, true, GradientCompatMode::Moz)
|
||||||
},
|
},
|
||||||
|
"conic-gradient" if static_prefs::pref!("layout.css.conic-gradient.enabled") => {
|
||||||
|
(Shape::Conic, false, GradientCompatMode::Modern)
|
||||||
|
},
|
||||||
|
"repeating-conic-gradient" if static_prefs::pref!("layout.css.conic-gradient.enabled") => {
|
||||||
|
(Shape::Conic, true, GradientCompatMode::Modern)
|
||||||
|
},
|
||||||
"-webkit-gradient" => {
|
"-webkit-gradient" => {
|
||||||
return input.parse_nested_block(|i| {
|
return input.parse_nested_block(|i| {
|
||||||
Self::parse_webkit_gradient_argument(context, i)
|
Self::parse_webkit_gradient_argument(context, i)
|
||||||
|
@ -247,6 +263,7 @@ impl Parse for Gradient {
|
||||||
Ok(match shape {
|
Ok(match shape {
|
||||||
Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?,
|
Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?,
|
||||||
Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?,
|
Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?,
|
||||||
|
Shape::Conic => Self::parse_conic(context, i, repeating)?,
|
||||||
})
|
})
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
@ -505,12 +522,12 @@ impl Gradient {
|
||||||
Ok(items.into())
|
Ok(items.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Not used for -webkit-gradient syntax.
|
/// Not used for -webkit-gradient syntax and conic-gradient
|
||||||
fn parse_stops<'i, 't>(
|
fn parse_stops<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
) -> Result<LengthPercentageItemList, ParseError<'i>> {
|
) -> Result<LengthPercentageItemList, ParseError<'i>> {
|
||||||
let items = generic::GradientItem::parse_comma_separated(context, input)?;
|
let items = generic::GradientItem::parse_comma_separated(context, input, LengthPercentage::parse)?;
|
||||||
if items.len() < 2 {
|
if items.len() < 2 {
|
||||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||||
}
|
}
|
||||||
|
@ -595,6 +612,40 @@ impl Gradient {
|
||||||
compat_mode,
|
compat_mode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
fn parse_conic<'i, 't>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
repeating: bool,
|
||||||
|
) -> Result<Self, ParseError<'i>> {
|
||||||
|
let angle = input.try(|i| {
|
||||||
|
i.expect_ident_matching("from")?;
|
||||||
|
// Spec allows unitless zero start angles
|
||||||
|
// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle
|
||||||
|
Angle::parse_with_unitless(context, i)
|
||||||
|
});
|
||||||
|
let position = input.try(|i| {
|
||||||
|
i.expect_ident_matching("at")?;
|
||||||
|
Position::parse(context, i)
|
||||||
|
});
|
||||||
|
if angle.is_ok() || position.is_ok() {
|
||||||
|
input.expect_comma()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let angle = angle.unwrap_or(Angle::zero());
|
||||||
|
let position = position.unwrap_or(Position::center());
|
||||||
|
let items = generic::GradientItem::parse_comma_separated(context, input, AngleOrPercentage::parse_with_unitless)?;
|
||||||
|
|
||||||
|
if items.len() < 2 {
|
||||||
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Gradient::Conic {
|
||||||
|
angle,
|
||||||
|
position,
|
||||||
|
items,
|
||||||
|
repeating,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl generic::LineDirection for LineDirection {
|
impl generic::LineDirection for LineDirection {
|
||||||
|
@ -798,13 +849,11 @@ impl ShapeExtent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> generic::GradientItem<Color, T>
|
impl<T> generic::GradientItem<Color, T> {
|
||||||
where
|
|
||||||
T: Parse,
|
|
||||||
{
|
|
||||||
fn parse_comma_separated<'i, 't>(
|
fn parse_comma_separated<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
|
parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result<T, ParseError<'i1>> + Copy,
|
||||||
) -> Result<crate::OwnedSlice<Self>, ParseError<'i>> {
|
) -> Result<crate::OwnedSlice<Self>, ParseError<'i>> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
let mut seen_stop = false;
|
let mut seen_stop = false;
|
||||||
|
@ -812,16 +861,16 @@ where
|
||||||
loop {
|
loop {
|
||||||
input.parse_until_before(Delimiter::Comma, |input| {
|
input.parse_until_before(Delimiter::Comma, |input| {
|
||||||
if seen_stop {
|
if seen_stop {
|
||||||
if let Ok(hint) = input.try(|i| T::parse(context, i)) {
|
if let Ok(hint) = input.try(|i| parse_position(context, i)) {
|
||||||
seen_stop = false;
|
seen_stop = false;
|
||||||
items.push(generic::GradientItem::InterpolationHint(hint));
|
items.push(generic::GradientItem::InterpolationHint(hint));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let stop = generic::ColorStop::parse(context, input)?;
|
let stop = generic::ColorStop::parse(context, input, parse_position)?;
|
||||||
|
|
||||||
if let Ok(multi_position) = input.try(|i| T::parse(context, i)) {
|
if let Ok(multi_position) = input.try(|i| parse_position(context, i)) {
|
||||||
let stop_color = stop.color.clone();
|
let stop_color = stop.color.clone();
|
||||||
items.push(stop.into_item());
|
items.push(stop.into_item());
|
||||||
items.push(
|
items.push(
|
||||||
|
@ -853,17 +902,15 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Parse for generic::ColorStop<Color, T>
|
impl<T> generic::ColorStop<Color, T> {
|
||||||
where
|
|
||||||
T: Parse,
|
|
||||||
{
|
|
||||||
fn parse<'i, 't>(
|
fn parse<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
|
parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result<T, ParseError<'i1>>,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
Ok(generic::ColorStop {
|
Ok(generic::ColorStop {
|
||||||
color: Color::parse(context, input)?,
|
color: Color::parse(context, input)?,
|
||||||
position: input.try(|i| T::parse(context, i)).ok(),
|
position: input.try(|i| parse_position(context, i)).ok(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue