diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 1899a9799e1..6584d4cbdc8 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -363,10 +363,12 @@ pub mod basic_shape { use gecko_bindings::sugar::ns_style_coord::{CoordDataMut, CoordDataValue}; use std::borrow::Borrow; use values::computed::{BorderRadiusSize, LengthOrPercentage}; - use values::computed::basic_shape::*; + use values::computed::basic_shape::{BasicShape, BorderRadius, ShapeRadius}; use values::computed::position; use values::generics::BorderRadiusSize as GenericBorderRadiusSize; - use values::generics::basic_shape::FillRule; + use values::generics::basic_shape::{BasicShape as GenericBasicShape, InsetRect, Polygon}; + use values::generics::basic_shape::{Circle, Ellipse, FillRule}; + use values::generics::basic_shape::{GeometryBox, ShapeBox}; // using Borrow so that we can have a non-moving .into() impl> From for BasicShape { @@ -379,7 +381,7 @@ pub mod basic_shape { let b = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[2]); let l = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[3]); let round = (&other.mRadius).into(); - BasicShape::Inset(InsetRect { + GenericBasicShape::Inset(InsetRect { top: t.expect("inset() offset should be a length, percentage, or calc value"), right: r.expect("inset() offset should be a length, percentage, or calc value"), bottom: b.expect("inset() offset should be a length, percentage, or calc value"), @@ -388,13 +390,13 @@ pub mod basic_shape { }) } StyleBasicShapeType::Circle => { - BasicShape::Circle(Circle { + GenericBasicShape::Circle(Circle { radius: (&other.mCoordinates[0]).into(), position: (&other.mPosition).into() }) } StyleBasicShapeType::Ellipse => { - BasicShape::Ellipse(Ellipse { + GenericBasicShape::Ellipse(Ellipse { semiaxis_x: (&other.mCoordinates[0]).into(), semiaxis_y: (&other.mCoordinates[1]).into(), position: (&other.mPosition).into() @@ -416,7 +418,7 @@ pub mod basic_shape { .expect("polygon() coordinate should be a length, percentage, or calc value") )) } - BasicShape::Polygon(Polygon { + GenericBasicShape::Polygon(Polygon { fill: fill_rule, coordinates: coords, }) diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 2b10b725d64..fa7d5c67271 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -3853,8 +3853,7 @@ fn static_assert() { use gecko_bindings::structs::{StyleFillRule, StyleGeometryBox, StyleShapeSource}; use gecko::conversions::basic_shape::set_corners_from_radius; use gecko::values::GeckoStyleCoordConvertible; - use values::computed::basic_shape::BasicShape; - use values::generics::basic_shape::{ShapeSource, FillRule}; + use values::generics::basic_shape::{BasicShape, FillRule, ShapeSource}; let ref mut ${ident} = self.gecko.${gecko_ffi_name}; // clean up existing struct unsafe { Gecko_DestroyShapeSource(${ident}) }; diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index fe9aa583a2b..46897b1e0a1 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -2432,7 +2432,7 @@ ${helpers.single_keyword("-moz-orient", } -${helpers.predefined_type("shape-outside", "basic_shape::ShapeWithShapeBox", +${helpers.predefined_type("shape-outside", "basic_shape::FloatAreaShape", "generics::basic_shape::ShapeSource::None", products="gecko", boxed="True", animation_value_type="none", diff --git a/components/style/properties/longhand/svg.mako.rs b/components/style/properties/longhand/svg.mako.rs index 9d531ca58a1..801c376c8ed 100644 --- a/components/style/properties/longhand/svg.mako.rs +++ b/components/style/properties/longhand/svg.mako.rs @@ -58,7 +58,7 @@ ${helpers.single_keyword("mask-type", "luminance alpha", products="gecko", animation_value_type="none", spec="https://drafts.fxtf.org/css-masking/#propdef-mask-type")} -${helpers.predefined_type("clip-path", "basic_shape::ShapeWithGeometryBox", +${helpers.predefined_type("clip-path", "basic_shape::ClippingShape", "generics::basic_shape::ShapeSource::None", products="gecko", boxed="True", animation_value_type="none", flags="CREATES_STACKING_CONTEXT", diff --git a/components/style/values/computed/basic_shape.rs b/components/style/values/computed/basic_shape.rs index eb5d67a98db..1e9ca6c5ff8 100644 --- a/components/style/values/computed/basic_shape.rs +++ b/components/style/values/computed/basic_shape.rs @@ -10,90 +10,55 @@ use std::fmt; use style_traits::ToCss; use values::computed::LengthOrPercentage; -use values::computed::position::Position; -use values::generics::basic_shape::{BorderRadius as GenericBorderRadius, ShapeRadius as GenericShapeRadius}; -use values::generics::basic_shape::{InsetRect as GenericInsetRect, Polygon as GenericPolygon, ShapeSource}; +use values::generics::basic_shape::{BasicShape as GenericBasicShape, BorderRadius as GenericBorderRadius}; +use values::generics::basic_shape::{Circle as GenericCircle, ClippingShape as GenericClippingShape}; +use values::generics::basic_shape::{Ellipse as GenericEllipse, FloatAreaShape as GenericFloatAreaShape}; +use values::generics::basic_shape::{InsetRect as GenericInsetRect, ShapeRadius as GenericShapeRadius}; -pub use values::generics::basic_shape::FillRule; -pub use values::specified::basic_shape::{self, GeometryBox, ShapeBox}; +/// A specified clipping shape. +pub type ClippingShape = GenericClippingShape; -/// The computed value used by `clip-path` -pub type ShapeWithGeometryBox = ShapeSource; +/// A specified float area shape. +pub type FloatAreaShape = GenericFloatAreaShape; -/// The computed value used by `shape-outside` -pub type ShapeWithShapeBox = ShapeSource; - -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum BasicShape { - Inset(InsetRect), - Circle(Circle), - Ellipse(Ellipse), - Polygon(Polygon), -} - -impl ToCss for BasicShape { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - BasicShape::Inset(ref rect) => rect.to_css(dest), - BasicShape::Circle(ref circle) => circle.to_css(dest), - BasicShape::Ellipse(ref e) => e.to_css(dest), - BasicShape::Polygon(ref poly) => poly.to_css(dest), - } - } -} +/// A computed basic shape. +pub type BasicShape = GenericBasicShape; /// The computed value of `inset()` pub type InsetRect = GenericInsetRect; -#[derive(Clone, PartialEq, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct Circle { - pub radius: ShapeRadius, - pub position: Position, -} - -impl ToCss for Circle { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(self.radius.to_css(dest)); - try!(dest.write_str(" at ")); - self.position.to_css(dest) - } -} - -#[derive(Clone, PartialEq, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct Ellipse { - pub semiaxis_x: ShapeRadius, - pub semiaxis_y: ShapeRadius, - pub position: Position, -} - -impl ToCss for Ellipse { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("ellipse(")); - if (self.semiaxis_x, self.semiaxis_y) != Default::default() { - try!(self.semiaxis_x.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.semiaxis_y.to_css(dest)); - try!(dest.write_str(" ")); - } - try!(dest.write_str("at ")); - try!(self.position.to_css(dest)); - dest.write_str(")") - } -} - -/// The computed value of `Polygon` -pub type Polygon = GenericPolygon; - /// The computed value of `BorderRadius` pub type BorderRadius = GenericBorderRadius; +/// A computed circle. +pub type Circle = GenericCircle; + +/// A computed ellipse. +pub type Ellipse = GenericEllipse; + /// The computed value of `ShapeRadius` pub type ShapeRadius = GenericShapeRadius; -impl Copy for ShapeRadius {} +impl ToCss for Circle { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str("circle(")?; + self.radius.to_css(dest)?; + dest.write_str(" at ")?; + self.position.to_css(dest) + } +} + +impl ToCss for Ellipse { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str("ellipse(")?; + if (self.semiaxis_x, self.semiaxis_y) != Default::default() { + self.semiaxis_x.to_css(dest)?; + dest.write_str(" ")?; + self.semiaxis_y.to_css(dest)?; + dest.write_str(" ")?; + } + dest.write_str("at ")?; + self.position.to_css(dest)?; + dest.write_str(")") + } +} diff --git a/components/style/values/generics/basic_shape.rs b/components/style/values/generics/basic_shape.rs index 31f434414f5..f5b87ec1dd1 100644 --- a/components/style/values/generics/basic_shape.rs +++ b/components/style/values/generics/basic_shape.rs @@ -5,31 +5,217 @@ //! CSS handling for the [`basic-shape`](https://drafts.csswg.org/css-shapes/#typedef-basic-shape) //! types that are generic over their `ToCss` implementations. -use cssparser::Parser; use euclid::size::Size2D; -use parser::{Parse, ParserContext}; use properties::shorthands::serialize_four_sides; -use std::ascii::AsciiExt; use std::fmt; use style_traits::{HasViewportPercentage, ToCss}; use values::computed::ComputedValueAsSpecified; use values::generics::BorderRadiusSize; +use values::generics::position::Position; use values::specified::url::SpecifiedUrl; +/// A clipping shape, for `clip-path`. +pub type ClippingShape = ShapeSource; + +/// https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum GeometryBox { + FillBox, + StrokeBox, + ViewBox, + ShapeBox(ShapeBox), +} +impl ComputedValueAsSpecified for GeometryBox {} + +/// A float area shape, for `shape-outside`. +pub type FloatAreaShape = ShapeSource; + +// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box +define_css_keyword_enum!(ShapeBox: + "margin-box" => MarginBox, + "border-box" => BorderBox, + "padding-box" => PaddingBox, + "content-box" => ContentBox +); +add_impls_for_keyword_enum!(ShapeBox); + +/// A shape source, for some reference box. +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, PartialEq, ToComputedValue)] +pub enum ShapeSource { + Url(SpecifiedUrl), + Shape(BasicShape, Option), + Box(ReferenceBox), + None, +} + +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, PartialEq, ToComputedValue)] +pub enum BasicShape { + Inset(InsetRect), + Circle(Circle), + Ellipse(Ellipse), + Polygon(Polygon), +} + +/// https://drafts.csswg.org/css-shapes/#funcdef-inset +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, PartialEq, ToComputedValue)] +pub struct InsetRect { + pub top: LengthOrPercentage, + pub right: LengthOrPercentage, + pub bottom: LengthOrPercentage, + pub left: LengthOrPercentage, + pub round: Option>, +} + /// A generic type used for `border-radius`, `outline-radius` and `inset()` values. /// /// https://drafts.csswg.org/css-backgrounds-3/#border-radius -#[derive(Clone, Debug, PartialEq, ToComputedValue)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct BorderRadius { +#[derive(Clone, Debug, PartialEq, ToComputedValue)] +pub struct BorderRadius { /// The top left radius. - pub top_left: BorderRadiusSize, + pub top_left: BorderRadiusSize, /// The top right radius. - pub top_right: BorderRadiusSize, + pub top_right: BorderRadiusSize, /// The bottom right radius. - pub bottom_right: BorderRadiusSize, + pub bottom_right: BorderRadiusSize, /// The bottom left radius. - pub bottom_left: BorderRadiusSize, + pub bottom_left: BorderRadiusSize, +} + +/// https://drafts.csswg.org/css-shapes/#funcdef-circle +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue)] +pub struct Circle { + pub position: Position, + pub radius: ShapeRadius, +} + +/// https://drafts.csswg.org/css-shapes/#funcdef-ellipse +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue)] +pub struct Ellipse { + pub position: Position, + pub semiaxis_x: ShapeRadius, + pub semiaxis_y: ShapeRadius, +} + +/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue)] +pub enum ShapeRadius { + Length(LengthOrPercentage), + ClosestSide, + FarthestSide, +} + +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, PartialEq, ToComputedValue)] +/// A generic type for representing the `polygon()` function +/// +/// https://drafts.csswg.org/css-shapes/#funcdef-polygon +pub struct Polygon { + /// The filling rule for a polygon. + pub fill: FillRule, + /// A collection of (x, y) coordinates to draw the polygon. + pub coordinates: Vec<(LengthOrPercentage, LengthOrPercentage)>, +} + +// https://drafts.csswg.org/css-shapes/#typedef-fill-rule +// NOTE: Basic shapes spec says that these are the only two values, however +// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty +// says that it can also be `inherit` +define_css_keyword_enum!(FillRule: + "nonzero" => NonZero, + "evenodd" => EvenOdd +); +add_impls_for_keyword_enum!(FillRule); + +impl HasViewportPercentage for ShapeSource { + #[inline] + fn has_viewport_percentage(&self) -> bool { false } +} + +impl ToCss for ShapeSource { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + ShapeSource::Url(ref url) => url.to_css(dest), + ShapeSource::Shape(ref shape, Some(ref ref_box)) => { + shape.to_css(dest)?; + dest.write_str(" ")?; + ref_box.to_css(dest) + }, + ShapeSource::Shape(ref shape, None) => shape.to_css(dest), + ShapeSource::Box(ref val) => val.to_css(dest), + ShapeSource::None => dest.write_str("none"), + } + } +} + +impl ToCss for GeometryBox { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + GeometryBox::FillBox => dest.write_str("fill-box"), + GeometryBox::StrokeBox => dest.write_str("stroke-box"), + GeometryBox::ViewBox => dest.write_str("view-box"), + GeometryBox::ShapeBox(s) => s.to_css(dest), + } + } +} + +impl ToCss for BasicShape + where H: ToCss, + V: ToCss, + L: PartialEq + ToCss, + Circle: ToCss, + Ellipse: ToCss, +{ + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + BasicShape::Inset(ref rect) => rect.to_css(dest), + BasicShape::Circle(ref circle) => circle.to_css(dest), + BasicShape::Ellipse(ref ellipse) => ellipse.to_css(dest), + BasicShape::Polygon(ref polygon) => polygon.to_css(dest), + } + } +} + +impl ToCss for InsetRect { + // XXXManishearth We should try to reduce the number of values printed here + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str("inset(")?; + self.top.to_css(dest)?; + dest.write_str(" ")?; + self.right.to_css(dest)?; + dest.write_str(" ")?; + self.bottom.to_css(dest)?; + dest.write_str(" ")?; + self.left.to_css(dest)?; + if let Some(ref radius) = self.round { + dest.write_str(" round ")?; + radius.to_css(dest)?; + } + + dest.write_str(")") + } +} + +impl ToCss for BorderRadius { + #[inline] + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + serialize_radius_values(dest, &self.top_left.0, &self.top_right.0, + &self.bottom_right.0, &self.bottom_left.0) + } } /// Serialization helper for types of longhands like `border-radius` and `outline-radius` @@ -51,24 +237,6 @@ pub fn serialize_radius_values(dest: &mut W, top_left: &Size2D, } } -impl ToCss for BorderRadius { - #[inline] - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - serialize_radius_values(dest, &self.top_left.0, &self.top_right.0, - &self.bottom_right.0, &self.bottom_left.0) - } -} - -/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius -#[derive(Clone, Debug, PartialEq, ToComputedValue)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum ShapeRadius { - Length(L), - ClosestSide, - FarthestSide, -} - impl Default for ShapeRadius { #[inline] fn default() -> Self { ShapeRadius::ClosestSide } @@ -85,64 +253,6 @@ impl ToCss for ShapeRadius { } } -// https://drafts.csswg.org/css-shapes/#typedef-fill-rule -// NOTE: Basic shapes spec says that these are the only two values, however -// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty -// says that it can also be `inherit` -define_css_keyword_enum!(FillRule: - "nonzero" => NonZero, - "evenodd" => EvenOdd -); - -impl ComputedValueAsSpecified for FillRule {} - -impl Default for FillRule { - #[inline] - fn default() -> Self { FillRule::NonZero } -} - -#[derive(Clone, Debug, PartialEq, ToComputedValue)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// A generic type for representing the `polygon()` function -/// -/// https://drafts.csswg.org/css-shapes/#funcdef-polygon -pub struct Polygon { - /// The filling rule for a polygon. - pub fill: FillRule, - /// A collection of (x, y) coordinates to draw the polygon. - pub coordinates: Vec<(L, L)>, -} - -impl Polygon { - /// Parse the inner arguments of a `polygon` function. - pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { - let fill = input.try(|i| -> Result<_, ()> { - let fill = FillRule::parse(i)?; - i.expect_comma()?; // only eat the comma if there is something before it - Ok(fill) - }).ok().unwrap_or_default(); - - let buf = input.parse_comma_separated(|i| { - Ok((L::parse(context, i)?, L::parse(context, i)?)) - })?; - - Ok(Polygon { - fill: fill, - coordinates: buf, - }) - } -} - -impl Parse for Polygon { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - match input.expect_function() { - Ok(ref s) if s.eq_ignore_ascii_case("polygon") => - input.parse_nested_block(|i| Polygon::parse_function_arguments(context, i)), - _ => Err(()) - } - } -} - impl ToCss for Polygon { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { dest.write_str("polygon(")?; @@ -165,105 +275,7 @@ impl ToCss for Polygon { } } -#[derive(Clone, Debug, PartialEq, ToComputedValue)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// https://drafts.csswg.org/css-shapes/#funcdef-inset -#[allow(missing_docs)] -pub struct InsetRect { - pub top: L, - pub right: L, - pub bottom: L, - pub left: L, - pub round: Option>, -} - -impl ToCss for InsetRect { - // XXXManishearth We should try to reduce the number of values printed here - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - dest.write_str("inset(")?; - self.top.to_css(dest)?; - dest.write_str(" ")?; - self.right.to_css(dest)?; - dest.write_str(" ")?; - self.bottom.to_css(dest)?; - dest.write_str(" ")?; - self.left.to_css(dest)?; - if let Some(ref radius) = self.round { - dest.write_str(" round ")?; - radius.to_css(dest)?; - } - - dest.write_str(")") - } -} - -/// A shape source, for some reference box -/// -/// `clip-path` uses ShapeSource, -/// `shape-outside` uses ShapeSource -#[derive(Clone, Debug, PartialEq, ToComputedValue)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum ShapeSource { - Url(SpecifiedUrl), - Shape(B, Option), - Box(T), - None, -} - -impl HasViewportPercentage for ShapeSource { +impl Default for FillRule { #[inline] - fn has_viewport_percentage(&self) -> bool { false } -} - -impl ToCss for ShapeSource { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - ShapeSource::Url(ref url) => url.to_css(dest), - ShapeSource::Shape(ref shape, Some(ref ref_box)) => { - shape.to_css(dest)?; - dest.write_str(" ")?; - ref_box.to_css(dest) - }, - ShapeSource::Shape(ref shape, None) => shape.to_css(dest), - ShapeSource::Box(ref val) => val.to_css(dest), - ShapeSource::None => dest.write_str("none"), - } - } -} - -impl Parse for ShapeSource { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - if input.try(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(ShapeSource::None) - } - - if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) { - return Ok(ShapeSource::Url(url)) - } - - fn parse_component(context: &ParserContext, input: &mut Parser, - component: &mut Option) -> bool { - if component.is_some() { - return false // already parsed this component - } - - *component = input.try(|i| U::parse(context, i)).ok(); - component.is_some() - } - - let mut shape = None; - let mut ref_box = None; - - while parse_component(context, input, &mut shape) || - parse_component(context, input, &mut ref_box) { - // - } - - if let Some(shp) = shape { - return Ok(ShapeSource::Shape(shp, ref_box)) - } - - ref_box.map(|v| ShapeSource::Box(v)).ok_or(()) - } + fn default() -> Self { FillRule::NonZero } } diff --git a/components/style/values/specified/basic_shape.rs b/components/style/values/specified/basic_shape.rs index f3da0341a1e..02729e0e635 100644 --- a/components/style/values/specified/basic_shape.rs +++ b/components/style/values/specified/basic_shape.rs @@ -10,120 +10,272 @@ use cssparser::Parser; use parser::{Parse, ParserContext}; use properties::shorthands::parse_four_sides; -use std::ascii::AsciiExt; use std::borrow::Cow; use std::fmt; use style_traits::ToCss; -use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue}; -use values::computed::basic_shape as computed_basic_shape; use values::generics::BorderRadiusSize; -use values::generics::basic_shape::{BorderRadius as GenericBorderRadius, ShapeRadius as GenericShapeRadius}; -use values::generics::basic_shape::{InsetRect as GenericInsetRect, Polygon as GenericPolygon, ShapeSource}; -use values::specified::{LengthOrPercentage, Percentage, Position, PositionComponent}; -use values::specified::position::Side; +use values::generics::basic_shape::{BorderRadius as GenericBorderRadius, Circle as GenericCircle}; +use values::generics::basic_shape::{ClippingShape as GenericClippingShape, Ellipse as GenericEllipse}; +use values::generics::basic_shape::{FillRule, BasicShape as GenericBasicShape}; +use values::generics::basic_shape::{FloatAreaShape as GenericFloatAreaShape, InsetRect as GenericInsetRect}; +use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource}; +use values::generics::basic_shape::{Polygon as GenericPolygon, ShapeRadius as GenericShapeRadius}; +use values::specified::{LengthOrPercentage, Percentage}; +use values::specified::position::{HorizontalPosition, Position, PositionComponent, Side, VerticalPosition}; +use values::specified::url::SpecifiedUrl; -/// The specified value used by `clip-path` -pub type ShapeWithGeometryBox = ShapeSource; +/// A specified clipping shape. +pub type ClippingShape = GenericClippingShape; -/// The specified value used by `shape-outside` -pub type ShapeWithShapeBox = ShapeSource; +/// A specified float area shape. +pub type FloatAreaShape = GenericFloatAreaShape; -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum BasicShape { - Inset(InsetRect), - Circle(Circle), - Ellipse(Ellipse), - Polygon(Polygon), +/// A specified basic shape. +pub type BasicShape = GenericBasicShape; + +/// The specified value of `inset()` +pub type InsetRect = GenericInsetRect; + +/// The specified value of `BorderRadius` +pub type BorderRadius = GenericBorderRadius; + +/// A specified circle. +pub type Circle = GenericCircle; + +/// A specified ellipse. +pub type Ellipse = GenericEllipse; + +/// The specified value of `ShapeRadius` +pub type ShapeRadius = GenericShapeRadius; + +/// The specified value of `Polygon` +pub type Polygon = GenericPolygon; + +impl Parse for ShapeSource { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + if input.try(|i| i.expect_ident_matching("none")).is_ok() { + return Ok(ShapeSource::None) + } + + if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) { + return Ok(ShapeSource::Url(url)) + } + + fn parse_component(context: &ParserContext, input: &mut Parser, + component: &mut Option) -> bool { + if component.is_some() { + return false // already parsed this component + } + + *component = input.try(|i| U::parse(context, i)).ok(); + component.is_some() + } + + let mut shape = None; + let mut ref_box = None; + + while parse_component(context, input, &mut shape) || + parse_component(context, input, &mut ref_box) { + // + } + + if let Some(shp) = shape { + return Ok(ShapeSource::Shape(shp, ref_box)) + } + + ref_box.map(|v| ShapeSource::Box(v)).ok_or(()) + } } -impl Parse for BasicShape { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - match_ignore_ascii_case! { &input.try(|i| i.expect_function())?, - "inset" => - input.parse_nested_block(|i| InsetRect::parse_function_arguments(context, i)) - .map(BasicShape::Inset), - "circle" => - input.parse_nested_block(|i| Circle::parse_function_arguments(context, i)) - .map(BasicShape::Circle), - "ellipse" => - input.parse_nested_block(|i| Ellipse::parse_function_arguments(context, i)) - .map(BasicShape::Ellipse), - "polygon" => - input.parse_nested_block(|i| Polygon::parse_function_arguments(context, i)) - .map(BasicShape::Polygon), +impl Parse for GeometryBox { + fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + if let Ok(shape_box) = input.try(|i| ShapeBox::parse(i)) { + return Ok(GeometryBox::ShapeBox(shape_box)) + } + + match_ignore_ascii_case! { &input.expect_ident()?, + "fill-box" => Ok(GeometryBox::FillBox), + "stroke-box" => Ok(GeometryBox::StrokeBox), + "view-box" => Ok(GeometryBox::ViewBox), _ => Err(()) } } } -impl ToCss for BasicShape { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - BasicShape::Inset(ref rect) => rect.to_css(dest), - BasicShape::Circle(ref circle) => circle.to_css(dest), - BasicShape::Ellipse(ref e) => e.to_css(dest), - BasicShape::Polygon(ref poly) => poly.to_css(dest), - } - } -} - -impl ToComputedValue for BasicShape { - type ComputedValue = computed_basic_shape::BasicShape; - - #[inline] - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - match *self { - BasicShape::Inset(ref rect) => computed_basic_shape::BasicShape::Inset(rect.to_computed_value(cx)), - BasicShape::Circle(ref circle) => computed_basic_shape::BasicShape::Circle(circle.to_computed_value(cx)), - BasicShape::Ellipse(ref e) => computed_basic_shape::BasicShape::Ellipse(e.to_computed_value(cx)), - BasicShape::Polygon(ref poly) => computed_basic_shape::BasicShape::Polygon(poly.to_computed_value(cx)), - } - } - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - match *computed { - computed_basic_shape::BasicShape::Inset(ref rect) => - BasicShape::Inset(ToComputedValue::from_computed_value(rect)), - computed_basic_shape::BasicShape::Circle(ref circle) => - BasicShape::Circle(ToComputedValue::from_computed_value(circle)), - computed_basic_shape::BasicShape::Ellipse(ref e) => - BasicShape::Ellipse(ToComputedValue::from_computed_value(e)), - computed_basic_shape::BasicShape::Polygon(ref poly) => - BasicShape::Polygon(ToComputedValue::from_computed_value(poly)), - } - } -} - -/// The specified value of `inset()` -pub type InsetRect = GenericInsetRect; - -impl InsetRect { - /// Parse the inner function arguments of `inset()` - pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { - let (t, r, b, l) = parse_four_sides(input, |i| LengthOrPercentage::parse(context, i))?; - let mut rect = GenericInsetRect { - top: t, - right: r, - bottom: b, - left: l, - round: None, - }; - - if input.try(|i| i.expect_ident_matching("round")).is_ok() { - rect.round = Some(BorderRadius::parse(context, input)?); - } - - Ok(rect) +impl Parse for BasicShape { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + let function = input.expect_function()?; + input.parse_nested_block(|i| { + match_ignore_ascii_case! { &function, + "inset" => InsetRect::parse_function_arguments(context, i).map(GenericBasicShape::Inset), + "circle" => Circle::parse_function_arguments(context, i).map(GenericBasicShape::Circle), + "ellipse" => Ellipse::parse_function_arguments(context, i).map(GenericBasicShape::Ellipse), + "polygon" => Polygon::parse_function_arguments(context, i).map(GenericBasicShape::Polygon), + _ => Err(()) + } + }) } } impl Parse for InsetRect { fn parse(context: &ParserContext, input: &mut Parser) -> Result { - match input.try(|i| i.expect_function()) { - Ok(ref s) if s.eq_ignore_ascii_case("inset") => - input.parse_nested_block(|i| GenericInsetRect::parse_function_arguments(context, i)), + input.expect_function_matching("inset")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) + } +} + +impl InsetRect { + /// Parse the inner function arguments of `inset()` + pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { + let (t, r, b, l) = parse_four_sides(input, |i| LengthOrPercentage::parse(context, i))?; + let rect = if input.try(|i| i.expect_ident_matching("round")).is_ok() { + Some(BorderRadius::parse(context, input)?) + } else { + None + }; + Ok(GenericInsetRect { + top: t, + right: r, + bottom: b, + left: l, + round: rect, + }) + } +} + +impl Parse for BorderRadius { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + let mut widths = parse_one_set_of_border_values(context, input)?; + let mut heights = if input.try(|input| input.expect_delim('/')).is_ok() { + parse_one_set_of_border_values(context, input)? + } else { + [widths[0].clone(), + widths[1].clone(), + widths[2].clone(), + widths[3].clone()] + }; + + Ok(BorderRadius { + top_left: BorderRadiusSize::new(widths[0].take(), heights[0].take()), + top_right: BorderRadiusSize::new(widths[1].take(), heights[1].take()), + bottom_right: BorderRadiusSize::new(widths[2].take(), heights[2].take()), + bottom_left: BorderRadiusSize::new(widths[3].take(), heights[3].take()), + }) + } +} + +fn parse_one_set_of_border_values(context: &ParserContext, mut input: &mut Parser) + -> Result<[LengthOrPercentage; 4], ()> { + let a = try!(LengthOrPercentage::parse_non_negative(context, input)); + let b = if let Ok(b) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { + b + } else { + return Ok([a.clone(), a.clone(), a.clone(), a]) + }; + + let c = if let Ok(c) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { + c + } else { + return Ok([a.clone(), b.clone(), a, b]) + }; + + if let Ok(d) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { + Ok([a, b, c, d]) + } else { + Ok([a, b.clone(), c, b]) + } +} + +impl Parse for Circle { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + input.expect_function_matching("circle")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) + } +} + +impl Circle { + #[allow(missing_docs)] + pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { + let radius = input.try(|i| ShapeRadius::parse(context, i)).ok().unwrap_or_default(); + let position = if input.try(|i| i.expect_ident_matching("at")).is_ok() { + Position::parse(context, input)? + } else { + Position::center() + }; + + Ok(GenericCircle { + radius: radius, + position: position, + }) + } +} + +impl ToCss for Circle { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str("circle(")?; + if GenericShapeRadius::ClosestSide != self.radius { + self.radius.to_css(dest)?; + dest.write_str(" ")?; + } + + dest.write_str("at ")?; + serialize_basicshape_position(&self.position, dest)?; + dest.write_str(")") + } +} + +impl Parse for Ellipse { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + input.expect_function_matching("ellipse")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) + } +} + +impl Ellipse { + #[allow(missing_docs)] + pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { + let (a, b) = input.try(|i| -> Result<_, ()> { + Ok((ShapeRadius::parse(context, i)?, ShapeRadius::parse(context, i)?)) + }).ok().unwrap_or_default(); + let position = if input.try(|i| i.expect_ident_matching("at")).is_ok() { + Position::parse(context, input)? + } else { + Position::center() + }; + + Ok(GenericEllipse { + semiaxis_x: a, + semiaxis_y: b, + position: position, + }) + } +} + +impl ToCss for Ellipse { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str("ellipse(")?; + if self.semiaxis_x != ShapeRadius::default() || self.semiaxis_y != ShapeRadius::default() { + self.semiaxis_x.to_css(dest)?; + dest.write_str(" ")?; + self.semiaxis_y.to_css(dest)?; + dest.write_str(" ")?; + } + + dest.write_str("at ")?; + serialize_basicshape_position(&self.position, dest)?; + dest.write_str(")") + } +} + +impl Parse for ShapeRadius { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { + return Ok(GenericShapeRadius::Length(lop)) + } + + match_ignore_ascii_case! { &input.expect_ident()?, + "closest-side" => Ok(GenericShapeRadius::ClosestSide), + "farthest-side" => Ok(GenericShapeRadius::FarthestSide), _ => Err(()) } } @@ -196,266 +348,29 @@ fn serialize_basicshape_position(position: &Position, dest: &mut W) -> fmt::R write_pair(&y_pos, &*y_lop, dest) } -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// https://drafts.csswg.org/css-shapes/#funcdef-circle -#[allow(missing_docs)] -pub struct Circle { - pub radius: ShapeRadius, - pub position: Position, -} - -impl Circle { - #[allow(missing_docs)] - pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { - let radius = input.try(|i| ShapeRadius::parse(context, i)).ok().unwrap_or_default(); - let position = if input.try(|i| i.expect_ident_matching("at")).is_ok() { - Position::parse(context, input)? - } else { - Position::center() // Defaults to origin - }; - - Ok(Circle { - radius: radius, - position: position, - }) - } -} - -impl Parse for Circle { +impl Parse for Polygon { fn parse(context: &ParserContext, input: &mut Parser) -> Result { - match_ignore_ascii_case! { &try!(input.expect_function()), - "circle" => { - input.parse_nested_block(|i| Circle::parse_function_arguments(context, i)) - }, - _ => Err(()) - } + input.expect_function_matching("polygon")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) } } -impl ToCss for Circle { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("circle(")); - if GenericShapeRadius::ClosestSide != self.radius { - try!(self.radius.to_css(dest)); - try!(dest.write_str(" ")); - } - - try!(dest.write_str("at ")); - try!(serialize_basicshape_position(&self.position, dest)); - dest.write_str(")") - } -} - -impl ToComputedValue for Circle { - type ComputedValue = computed_basic_shape::Circle; - - #[inline] - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - computed_basic_shape::Circle { - radius: self.radius.to_computed_value(cx), - position: self.position.to_computed_value(cx), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Circle { - radius: ToComputedValue::from_computed_value(&computed.radius), - position: ToComputedValue::from_computed_value(&computed.position), - } - } -} - -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// https://drafts.csswg.org/css-shapes/#funcdef-ellipse -#[allow(missing_docs)] -pub struct Ellipse { - pub semiaxis_x: ShapeRadius, - pub semiaxis_y: ShapeRadius, - pub position: Position, -} - -impl Ellipse { - #[allow(missing_docs)] - pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { - let (a, b) = input.try(|i| -> Result<_, ()> { - Ok((ShapeRadius::parse(context, i)?, ShapeRadius::parse(context, i)?)) +impl Polygon { + /// Parse the inner arguments of a `polygon` function. + pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { + let fill = input.try(|i| -> Result<_, ()> { + let fill = FillRule::parse(i)?; + i.expect_comma()?; // only eat the comma if there is something before it + Ok(fill) }).ok().unwrap_or_default(); - let position = if input.try(|i| i.expect_ident_matching("at")).is_ok() { - Position::parse(context, input)? - } else { - Position::center() // Defaults to origin - }; - Ok(Ellipse { - semiaxis_x: a, - semiaxis_y: b, - position: position, + let buf = input.parse_comma_separated(|i| { + Ok((LengthOrPercentage::parse(context, i)?, LengthOrPercentage::parse(context, i)?)) + })?; + + Ok(Polygon { + fill: fill, + coordinates: buf, }) } } - -impl Parse for Ellipse { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - match input.try(|i| i.expect_function()) { - Ok(ref s) if s.eq_ignore_ascii_case("ellipse") => - input.parse_nested_block(|i| Ellipse::parse_function_arguments(context, i)), - _ => Err(()) - } - } -} - -impl ToCss for Ellipse { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("ellipse(")); - if self.semiaxis_x != ShapeRadius::default() || self.semiaxis_y != ShapeRadius::default() { - try!(self.semiaxis_x.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.semiaxis_y.to_css(dest)); - try!(dest.write_str(" ")); - } - - try!(dest.write_str("at ")); - try!(serialize_basicshape_position(&self.position, dest)); - dest.write_str(")") - } -} - -impl ToComputedValue for Ellipse { - type ComputedValue = computed_basic_shape::Ellipse; - - #[inline] - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - computed_basic_shape::Ellipse { - semiaxis_x: self.semiaxis_x.to_computed_value(cx), - semiaxis_y: self.semiaxis_y.to_computed_value(cx), - position: self.position.to_computed_value(cx), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Ellipse { - semiaxis_x: ToComputedValue::from_computed_value(&computed.semiaxis_x), - semiaxis_y: ToComputedValue::from_computed_value(&computed.semiaxis_y), - position: ToComputedValue::from_computed_value(&computed.position), - } - } -} - -/// The specified value of `Polygon` -pub type Polygon = GenericPolygon; - -/// The specified value of `ShapeRadius` -pub type ShapeRadius = GenericShapeRadius; - -impl Parse for ShapeRadius { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { - return Ok(GenericShapeRadius::Length(lop)) - } - - match_ignore_ascii_case! { &input.expect_ident()?, - "closest-side" => Ok(GenericShapeRadius::ClosestSide), - "farthest-side" => Ok(GenericShapeRadius::FarthestSide), - _ => Err(()) - } - } -} - -/// The specified value of `BorderRadius` -pub type BorderRadius = GenericBorderRadius; - -impl Parse for BorderRadius { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let mut widths = parse_one_set_of_border_values(context, input)?; - let mut heights = if input.try(|input| input.expect_delim('/')).is_ok() { - parse_one_set_of_border_values(context, input)? - } else { - [widths[0].clone(), - widths[1].clone(), - widths[2].clone(), - widths[3].clone()] - }; - - Ok(BorderRadius { - top_left: BorderRadiusSize::new(widths[0].take(), heights[0].take()), - top_right: BorderRadiusSize::new(widths[1].take(), heights[1].take()), - bottom_right: BorderRadiusSize::new(widths[2].take(), heights[2].take()), - bottom_left: BorderRadiusSize::new(widths[3].take(), heights[3].take()), - }) - } -} - -fn parse_one_set_of_border_values(context: &ParserContext, mut input: &mut Parser) - -> Result<[LengthOrPercentage; 4], ()> { - let a = try!(LengthOrPercentage::parse_non_negative(context, input)); - let b = if let Ok(b) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { - b - } else { - return Ok([a.clone(), a.clone(), a.clone(), a]) - }; - - let c = if let Ok(c) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { - c - } else { - return Ok([a.clone(), b.clone(), a, b]) - }; - - if let Ok(d) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { - Ok([a, b, c, d]) - } else { - Ok([a, b.clone(), c, b]) - } -} - -/// https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box -#[derive(Clone, PartialEq, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum GeometryBox { - FillBox, - StrokeBox, - ViewBox, - ShapeBox(ShapeBox), -} - -impl Parse for GeometryBox { - fn parse(_context: &ParserContext, input: &mut Parser) -> Result { - if let Ok(shape_box) = input.try(|i| ShapeBox::parse(i)) { - return Ok(GeometryBox::ShapeBox(shape_box)) - } - - match_ignore_ascii_case! { &input.expect_ident()?, - "fill-box" => Ok(GeometryBox::FillBox), - "stroke-box" => Ok(GeometryBox::StrokeBox), - "view-box" => Ok(GeometryBox::ViewBox), - _ => Err(()) - } - } -} - -impl ToCss for GeometryBox { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - GeometryBox::FillBox => dest.write_str("fill-box"), - GeometryBox::StrokeBox => dest.write_str("stroke-box"), - GeometryBox::ViewBox => dest.write_str("view-box"), - GeometryBox::ShapeBox(s) => s.to_css(dest), - } - } -} - -impl ComputedValueAsSpecified for GeometryBox {} - -// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box -define_css_keyword_enum!(ShapeBox: - "margin-box" => MarginBox, - "border-box" => BorderBox, - "padding-box" => PaddingBox, - "content-box" => ContentBox -); - -add_impls_for_keyword_enum!(ShapeBox);