diff --git a/components/layout/model.rs b/components/layout/model.rs index a6d64e557ac..27f0b2ce7a7 100644 --- a/components/layout/model.rs +++ b/components/layout/model.rs @@ -16,6 +16,7 @@ use style::logical_geometry::{LogicalMargin, WritingMode}; use style::properties::ServoComputedValues; use style::values::computed::{BorderRadiusSize, LengthOrPercentageOrAuto}; use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrNone}; +use style::values::generics; /// A collapsible margin. See CSS 2.1 ยง 8.3.1. #[derive(Copy, Clone, Debug)] @@ -478,7 +479,7 @@ pub fn specified(length: LengthOrPercentage, containing_length: Au) -> Au { } pub fn specified_border_radius(radius: BorderRadiusSize, containing_length: Au) -> Size2D { - let BorderRadiusSize(size) = radius; + let generics::BorderRadiusSize(size) = radius; let w = specified(size.width, containing_length); let h = specified(size.height, containing_length); Size2D::new(w, h) diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 0a14a5c3410..42c146f7125 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -273,8 +273,8 @@ impl nsStyleImage { }, } unsafe { - (*gecko_gradient).mBgPosX.set(position.horizontal); - (*gecko_gradient).mBgPosY.set(position.vertical); + (*gecko_gradient).mBgPosX.set(position.horizontal.0); + (*gecko_gradient).mBgPosY.set(position.vertical.0); } gecko_gradient @@ -318,7 +318,6 @@ impl nsStyleImage { pub mod basic_shape { //! Conversions from and to CSS shape representations. - use euclid::size::Size2D; use gecko::values::GeckoStyleCoordConvertible; use gecko_bindings::structs; use gecko_bindings::structs::{StyleBasicShape, StyleBasicShapeType, StyleFillRule}; @@ -329,6 +328,9 @@ pub mod basic_shape { use values::computed::{BorderRadiusSize, LengthOrPercentage}; use values::computed::basic_shape::*; use values::computed::position; + use values::generics::BorderRadiusSize as GenericBorderRadiusSize; + use values::generics::basic_shape::FillRule; + use values::generics::position::{HorizontalPosition, VerticalPosition}; // using Borrow so that we can have a non-moving .into() impl> From for BasicShape { @@ -391,11 +393,11 @@ pub mod basic_shape { fn from(other: T) -> Self { let other = other.borrow(); let get_corner = |index| { - BorderRadiusSize(Size2D::new( + GenericBorderRadiusSize::new( LengthOrPercentage::from_gecko_style_coord(&other.data_at(index)) .expect(" should be a length, percentage, or calc value"), LengthOrPercentage::from_gecko_style_coord(&other.data_at(index + 1)) - .expect(" should be a length, percentage, or calc value"))) + .expect(" should be a length, percentage, or calc value")) }; BorderRadius { @@ -439,8 +441,8 @@ pub mod basic_shape { impl From for structs::Position { fn from(other: position::Position) -> Self { structs::Position { - mXPosition: other.horizontal.into(), - mYPosition: other.vertical.into() + mXPosition: other.horizontal.0.into(), + mYPosition: other.vertical.0.into() } } } @@ -457,8 +459,8 @@ pub mod basic_shape { fn from(other: T) -> Self { let other = other.borrow(); position::Position { - horizontal: other.mXPosition.into(), - vertical: other.mYPosition.into(), + horizontal: HorizontalPosition(other.mXPosition.into()), + vertical: VerticalPosition(other.mYPosition.into()), } } } diff --git a/components/style/gecko/values.rs b/components/style/gecko/values.rs index 64bef9d86be..cf0e55d21f1 100644 --- a/components/style/gecko/values.rs +++ b/components/style/gecko/values.rs @@ -15,7 +15,8 @@ use values::{Auto, Either, ExtremumLength, None_, Normal}; use values::computed::{Angle, LengthOrPercentage, LengthOrPercentageOrAuto}; use values::computed::{LengthOrPercentageOrNone, Number, NumberOrPercentage}; use values::computed::{MaxLength, MinLength}; -use values::computed::basic_shape::ShapeRadius; +use values::computed::basic_shape::ShapeRadius as ComputedShapeRadius; +use values::generics::basic_shape::ShapeRadius; use values::specified::Percentage; use values::specified::grid::{TrackBreadth, TrackKeyword}; @@ -205,15 +206,13 @@ impl GeckoStyleCoordConvertible for TrackBreadth< } } -impl GeckoStyleCoordConvertible for ShapeRadius { +impl GeckoStyleCoordConvertible for ComputedShapeRadius { fn to_gecko_style_coord(&self, coord: &mut T) { match *self { - ShapeRadius::ClosestSide => { - coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::ClosestSide as u32)) - } - ShapeRadius::FarthestSide => { - coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::FarthestSide as u32)) - } + ShapeRadius::ClosestSide => + coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::ClosestSide as u32)), + ShapeRadius::FarthestSide => + coord.set_value(CoordDataValue::Enumerated(StyleShapeRadius::FarthestSide as u32)), ShapeRadius::Length(lop) => lop.to_gecko_style_coord(coord), } } diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 3c2ab7280be..9d49387322e 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -376,17 +376,17 @@ fn color_to_nscolor_zero_currentcolor(color: Color) -> structs::nscolor { <%def name="impl_position(ident, gecko_ffi_name, need_clone=False)"> #[allow(non_snake_case)] pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { - ${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.into()")} - ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.into()")} + ${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.0.into()")} + ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.0.into()")} } <%call expr="impl_simple_copy(ident, gecko_ffi_name)"> % if need_clone: #[allow(non_snake_case)] pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - use values::computed::Position; + use values::generics::position::{HorizontalPosition, Position, VerticalPosition}; Position { - horizontal: self.gecko.${gecko_ffi_name}.mXPosition.into(), - vertical: self.gecko.${gecko_ffi_name}.mYPosition.into(), + horizontal: HorizontalPosition(self.gecko.${gecko_ffi_name}.mXPosition.into()), + vertical: VerticalPosition(self.gecko.${gecko_ffi_name}.mYPosition.into()), } } % endif @@ -543,15 +543,14 @@ fn color_to_nscolor_zero_currentcolor(color: Color) -> structs::nscolor { % if need_clone: #[allow(non_snake_case)] pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - use properties::longhands::${ident}::computed_value::T; - use euclid::Size2D; + use values::generics::BorderRadiusSize; let width = GeckoStyleCoordConvertible::from_gecko_style_coord( &self.gecko.${gecko_ffi_name}.data_at(${x_index})) .expect("Failed to clone ${ident}"); let height = GeckoStyleCoordConvertible::from_gecko_style_coord( &self.gecko.${gecko_ffi_name}.data_at(${y_index})) .expect("Failed to clone ${ident}"); - T(Size2D::new(width, height)) + BorderRadiusSize::new(width, height) } % endif @@ -1948,8 +1947,8 @@ fn static_assert() { for (gecko, servo) in self.gecko.mScrollSnapCoordinate .iter_mut() .zip(v.0.iter()) { - gecko.mXPosition = servo.horizontal.into(); - gecko.mYPosition = servo.vertical.into(); + gecko.mXPosition = servo.horizontal.0.into(); + gecko.mYPosition = servo.vertical.0.into(); } } @@ -2601,7 +2600,7 @@ fn static_assert() { pub fn clone_${shorthand}_position_${orientation[0]}(&self) -> longhands::${shorthand}_position_${orientation[0]}::computed_value::T { - use values::computed::position::${orientation[1]}Position; + use values::generics::position::${orientation[1]}Position; longhands::${shorthand}_position_${orientation[0]}::computed_value::T( self.gecko.${image_layers_field}.mLayers.iter() .take(self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count as usize) @@ -3565,12 +3564,12 @@ fn static_assert() { <%def name="impl_shape_source(ident, gecko_ffi_name)"> pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { use gecko_bindings::bindings::{Gecko_NewBasicShape, Gecko_DestroyShapeSource}; - use gecko_bindings::structs::StyleGeometryBox; use gecko_bindings::structs::{StyleBasicShape, StyleBasicShapeType, StyleShapeSourceType}; - use gecko_bindings::structs::{StyleFillRule, StyleShapeSource}; + 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::*; + use values::computed::basic_shape::BasicShape; + use values::generics::basic_shape::{ShapeSource, FillRule}; let ref mut ${ident} = self.gecko.${gecko_ffi_name}; // clean up existing struct unsafe { Gecko_DestroyShapeSource(${ident}) }; @@ -3580,7 +3579,7 @@ fn static_assert() { match v { ShapeSource::Url(ref url) => { unsafe { - bindings::Gecko_StyleShapeSource_SetURLValue(${ident}, url.for_ffi()); + bindings::Gecko_StyleShapeSource_SetURLValue(${ident}, url.for_ffi()) } } ShapeSource::None => {} // don't change the type diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index bff29251830..5d13f91e282 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -8,14 +8,18 @@ %> <%def name="predefined_type(name, type, initial_value, parse_method='parse', - needs_context=True, vector=False, initial_specified_value=None, **kwargs)"> + needs_context=True, vector=False, computed_type=None, initial_specified_value=None, **kwargs)"> <%def name="predefined_type_inner(name, type, initial_value, parse_method)"> #[allow(unused_imports)] use app_units::Au; use cssparser::{Color as CSSParserColor, RGBA}; pub use values::specified::${type} as SpecifiedValue; pub mod computed_value { + % if computed_type: + pub use ${computed_type} as T; + % else: pub use values::computed::${type} as T; + % endif } #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} } % if initial_specified_value: @@ -36,10 +40,16 @@ % if vector: <%call expr="vector_longhand(name, predefined_type=type, **kwargs)"> ${predefined_type_inner(name, type, initial_value, parse_method)} + % if caller: + ${caller.body()} + % endif % else: <%call expr="longhand(name, predefined_type=type, **kwargs)"> ${predefined_type_inner(name, type, initial_value, parse_method)} + % if caller: + ${caller.body()} + % endif % endif @@ -238,7 +248,7 @@ use properties::style_structs; use std::sync::Arc; use values::computed::{Context, ToComputedValue}; - use values::{computed, specified}; + use values::{computed, generics, specified}; use Atom; ${caller.body()} #[allow(unused_variables)] diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index ed2e10b3a54..8783fc4a44f 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -31,14 +31,14 @@ use std::fmt; use style_traits::ToCss; use super::ComputedValues; use values::CSSFloat; -use values::{Auto, Either, Normal}; +use values::{Auto, Either, Normal, generics}; use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; use values::computed::{BorderRadiusSize, ClipRect, LengthOrNone}; use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage}; use values::computed::{MaxLength, MinLength}; -use values::computed::position::{HorizontalPosition, Position, VerticalPosition}; +use values::computed::position::{HorizontalPosition, VerticalPosition}; use values::computed::ToComputedValue; - +use values::generics::position as generic_position; /// A given transition property, that is either `All`, or an animatable @@ -711,7 +711,7 @@ impl Interpolate for Point2D { impl Interpolate for BorderRadiusSize { #[inline] fn interpolate(&self, other: &Self, progress: f64) -> Result { - self.0.interpolate(&other.0, progress).map(BorderRadiusSize) + self.0.interpolate(&other.0, progress).map(generics::BorderRadiusSize) } } @@ -957,23 +957,24 @@ impl Interpolate for FontWeight { } /// https://drafts.csswg.org/css-transitions/#animtype-simple-list -impl Interpolate for Position { +impl Interpolate for generic_position::Position { #[inline] fn interpolate(&self, other: &Self, progress: f64) -> Result { - Ok(Position { + Ok(generic_position::Position { horizontal: try!(self.horizontal.interpolate(&other.horizontal, progress)), vertical: try!(self.vertical.interpolate(&other.vertical, progress)), }) } } -impl RepeatableListInterpolate for Position {} +impl RepeatableListInterpolate for generic_position::Position + where H: RepeatableListInterpolate, V: RepeatableListInterpolate {} /// https://drafts.csswg.org/css-transitions/#animtype-simple-list impl Interpolate for HorizontalPosition { #[inline] fn interpolate(&self, other: &Self, progress: f64) -> Result { - Ok(HorizontalPosition(try!(self.0.interpolate(&other.0, progress)))) + self.0.interpolate(&other.0, progress).map(generic_position::HorizontalPosition) } } @@ -983,7 +984,7 @@ impl RepeatableListInterpolate for HorizontalPosition {} impl Interpolate for VerticalPosition { #[inline] fn interpolate(&self, other: &Self, progress: f64) -> Result { - Ok(VerticalPosition(try!(self.0.interpolate(&other.0, progress)))) + self.0.interpolate(&other.0, progress).map(generic_position::VerticalPosition) } } @@ -2607,7 +2608,9 @@ impl ComputeDistance for FontWeight { } } -impl ComputeDistance for Position { +impl ComputeDistance for generic_position::Position + where H: ComputeDistance, V: ComputeDistance +{ #[inline] fn compute_distance(&self, other: &Self) -> Result { self.compute_squared_distance(other).map(|sd| sd.sqrt()) diff --git a/components/style/properties/longhand/background.mako.rs b/components/style/properties/longhand/background.mako.rs index 96119dd2c02..726003f9571 100644 --- a/components/style/properties/longhand/background.mako.rs +++ b/components/style/properties/longhand/background.mako.rs @@ -89,108 +89,38 @@ ${helpers.predefined_type("background-color", "CSSColor", } -<%helpers:vector_longhand name="background-position-x" animation_value_type="ComputedValue" +<%helpers:predefined_type name="background-position-x" type="position::HorizontalPosition" + initial_value="computed::position::HorizontalPosition::zero()" + initial_specified_value="specified::position::HorizontalPosition::left()" spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x" - delegate_animate="True"> - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - use values::specified::position::HorizontalPosition; - - #[allow(missing_docs)] - pub mod computed_value { - use values::computed::position::HorizontalPosition; - use properties::animated_properties::{Interpolate, RepeatableListInterpolate}; - - pub type T = HorizontalPosition; - } - - #[allow(missing_docs)] - pub type SpecifiedValue = HorizontalPosition; - + animation_value_type="ComputedValue" vector="True" delegate_animate="True"> #[inline] - #[allow(missing_docs)] - pub fn get_initial_value() -> computed_value::T { - use values::computed::position::HorizontalPosition; - HorizontalPosition(computed::LengthOrPercentage::Percentage(0.0)) - } - #[inline] - #[allow(missing_docs)] - pub fn get_initial_specified_value() -> SpecifiedValue { - use values::specified::position::Keyword; - HorizontalPosition { - keyword: Some(Keyword::Left), - position: None, - } - } - #[inline] - #[allow(missing_docs)] + /// Get the initial value for horizontal position. pub fn get_initial_position_value() -> SpecifiedValue { + use values::generics::position::{HorizontalPosition, PositionValue}; use values::specified::{LengthOrPercentage, Percentage}; - HorizontalPosition { + HorizontalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Percentage(Percentage(0.0))), - } + }) } + - #[allow(missing_docs)] - pub fn parse(context: &ParserContext, input: &mut Parser) - -> Result { - HorizontalPosition::parse(context, input) - } - - -<%helpers:vector_longhand name="background-position-y" animation_value_type="ComputedValue" +<%helpers:predefined_type name="background-position-y" type="position::VerticalPosition" + initial_value="computed::position::VerticalPosition::zero()" + initial_specified_value="specified::position::VerticalPosition::top()" spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-y" - delegate_animate="True"> - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - use values::specified::position::VerticalPosition; - - #[allow(missing_docs)] - pub mod computed_value { - use values::computed::position::VerticalPosition; - use properties::animated_properties::{Interpolate, RepeatableListInterpolate}; - - pub type T = VerticalPosition; - } - - #[allow(missing_docs)] - pub type SpecifiedValue = VerticalPosition; - - #[inline] - #[allow(missing_docs)] - pub fn get_initial_value() -> computed_value::T { - use values::computed::position::VerticalPosition; - VerticalPosition(computed::LengthOrPercentage::Percentage(0.0)) - } - #[inline] - #[allow(missing_docs)] - pub fn get_initial_specified_value() -> SpecifiedValue { - use values::specified::position::Keyword; - VerticalPosition { - keyword: Some(Keyword::Top), - position: None, - } - } - #[inline] - #[allow(missing_docs)] + animation_value_type="ComputedValue" vector="True" delegate_animate="True"> + /// Get the initial value for vertical position. pub fn get_initial_position_value() -> SpecifiedValue { + use values::generics::position::{VerticalPosition, PositionValue}; use values::specified::{LengthOrPercentage, Percentage}; - VerticalPosition { + VerticalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Percentage(Percentage(0.0))), - } + }) } - - #[inline] - #[allow(missing_docs)] - pub fn parse(context: &ParserContext, input: &mut Parser) - -> Result { - VerticalPosition::parse(context, input) - } - + <%helpers:vector_longhand name="background-repeat" animation_value_type="none" spec="https://drafts.csswg.org/css-backgrounds/#the-background-repeat"> diff --git a/components/style/properties/longhand/border.mako.rs b/components/style/properties/longhand/border.mako.rs index 64c64c700fb..df5cb06f1cc 100644 --- a/components/style/properties/longhand/border.mako.rs +++ b/components/style/properties/longhand/border.mako.rs @@ -21,47 +21,24 @@ alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-color"), spec=maybe_logical_spec(side, "color"), animation_value_type="IntermediateColor", logical = side[1])} -% endfor -% for side in ALL_SIDES: ${helpers.predefined_type("border-%s-style" % side[0], "BorderStyle", "specified::BorderStyle::none", need_clone=True, alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-style"), spec=maybe_logical_spec(side, "style"), - animation_value_type="none", logical = side[1])} + animation_value_type="none", logical=side[1])} + + ${helpers.predefined_type("border-%s-width" % side[0], "BorderWidth", "Au::from_px(3)", + computed_type="::app_units::Au", + alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-width"), + spec=maybe_logical_spec(side, "width"), + animation_value_type="ComputedValue", logical=side[1])} % endfor ${helpers.gecko_keyword_conversion(Keyword('border-style', "none solid double dotted dashed hidden groove ridge inset outset"), type="::values::specified::BorderStyle")} -% for side in ALL_SIDES: - <%helpers:longhand name="border-${side[0]}-width" animation_value_type="ComputedValue" logical="${side[1]}" - alias="${maybe_moz_logical_alias(product, side, '-moz-border-%s-width')}" - spec="${maybe_logical_spec(side, 'width')}"> - use app_units::Au; - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - use values::specified::BorderWidth; - - pub type SpecifiedValue = BorderWidth; - - #[inline] - pub fn parse(context: &ParserContext, input: &mut Parser) - -> Result { - BorderWidth::parse(context, input) - } - - pub mod computed_value { - use app_units::Au; - pub type T = Au; - } - #[inline] pub fn get_initial_value() -> computed_value::T { - Au::from_px(3) // medium - } - -% endfor // FIXME(#4126): when gfx supports painting it, make this Size2D % for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]: diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 73adf4aa335..71d101b9c6e 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -1113,7 +1113,6 @@ ${helpers.predefined_type("scroll-snap-coordinate", delegate_animate=True)} - <%helpers:longhand name="transform" extra_prefixes="webkit" animation_value_type="ComputedValue" flags="CREATES_STACKING_CONTEXT FIXPOS_CB" @@ -2096,83 +2095,13 @@ ${helpers.predefined_type("perspective", flags="CREATES_STACKING_CONTEXT FIXPOS_CB", animation_value_type="ComputedValue")} -<%helpers:longhand name="perspective-origin" boxed="True" - animation_value_type="ComputedValue" - extra_prefixes="moz webkit" - spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property"> - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - use values::specified::{LengthOrPercentage, Percentage}; - - pub mod computed_value { - use properties::animated_properties::Interpolate; - use values::computed::LengthOrPercentage; - use values::computed::Position; - - pub type T = Position; - } - - impl HasViewportPercentage for SpecifiedValue { - fn has_viewport_percentage(&self) -> bool { - self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage() - } - } - - #[derive(Clone, Debug, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct SpecifiedValue { - horizontal: LengthOrPercentage, - vertical: LengthOrPercentage, - } - - impl ToCss for SpecifiedValue { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(self.horizontal.to_css(dest)); - try!(dest.write_str(" ")); - self.vertical.to_css(dest) - } - } - - #[inline] - pub fn get_initial_value() -> computed_value::T { - computed_value::T { - horizontal: computed::LengthOrPercentage::Percentage(0.5), - vertical: computed::LengthOrPercentage::Percentage(0.5), - } - } - - pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let result = try!(super::parse_origin(context, input)); - match result.depth { - Some(_) => Err(()), - None => Ok(SpecifiedValue { - horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), - vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), - }) - } - } - - impl ToComputedValue for SpecifiedValue { - type ComputedValue = computed_value::T; - - #[inline] - fn to_computed_value(&self, context: &Context) -> computed_value::T { - computed_value::T { - horizontal: self.horizontal.to_computed_value(context), - vertical: self.vertical.to_computed_value(context), - } - } - - #[inline] - fn from_computed_value(computed: &computed_value::T) -> Self { - SpecifiedValue { - horizontal: ToComputedValue::from_computed_value(&computed.horizontal), - vertical: ToComputedValue::from_computed_value(&computed.vertical), - } - } - } - +${helpers.predefined_type("perspective-origin", + "position::OriginPosition", + "computed::position::OriginPosition::center()", + boxed="True", + extra_prefixes="moz webkit", + spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property", + animation_value_type="ComputedValue")} ${helpers.single_keyword("backface-visibility", "visible hidden", @@ -2533,29 +2462,8 @@ ${helpers.single_keyword("-moz-orient", } -<%helpers:longhand name="shape-outside" products="gecko" animation_value_type="none" boxed="True" - spec="https://drafts.csswg.org/css-shapes/#shape-outside-property"> - use std::fmt; - use style_traits::ToCss; - use values::specified::basic_shape::{ShapeBox, ShapeSource}; - use values::HasViewportPercentage; - - no_viewport_percentage!(SpecifiedValue); - - pub mod computed_value { - use values::computed::basic_shape::{ShapeBox, ShapeSource}; - - pub type T = ShapeSource; - } - - pub type SpecifiedValue = ShapeSource; - - #[inline] - pub fn get_initial_value() -> computed_value::T { - Default::default() - } - - pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { - ShapeSource::parse(context, input) - } - +${helpers.predefined_type("shape-outside", "basic_shape::ShapeWithShapeBox", + "generics::basic_shape::ShapeSource::None", + products="gecko", boxed="True", + animation_value_type="none", + spec="https://drafts.csswg.org/css-shapes/#shape-outside-property")} diff --git a/components/style/properties/longhand/column.mako.rs b/components/style/properties/longhand/column.mako.rs index 64d8c86b7f4..91b42fa5350 100644 --- a/components/style/properties/longhand/column.mako.rs +++ b/components/style/properties/longhand/column.mako.rs @@ -40,36 +40,11 @@ ${helpers.single_keyword("column-fill", "balance auto", extra_prefixes="moz", products="gecko", animation_value_type="none", spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill")} -// https://drafts.csswg.org/css-multicol-1/#propdef-column-rule-width -<%helpers:longhand name="column-rule-width" products="gecko" animation_value_type="ComputedValue" extra_prefixes="moz" - spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width"> - use app_units::Au; - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - use values::specified::BorderWidth; - - pub mod computed_value { - use app_units::Au; - pub type T = Au; - } - - pub type SpecifiedValue = BorderWidth; - - #[inline] - pub fn get_initial_value() -> computed_value::T { - Au::from_px(3) // medium - } - - #[inline] - pub fn get_initial_specified_value() -> SpecifiedValue { - BorderWidth::Medium - } - - pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { - BorderWidth::parse(context, input) - } - +${helpers.predefined_type("column-rule-width", "BorderWidth", "Au::from_px(3)", + initial_specified_value="specified::BorderWidth::Medium", + products="gecko", computed_type="::app_units::Au", + spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width", + animation_value_type="ComputedValue", extra_prefixes="moz")} // https://drafts.csswg.org/css-multicol-1/#crc ${helpers.predefined_type("column-rule-color", "CSSColor", diff --git a/components/style/properties/longhand/inherited_text.mako.rs b/components/style/properties/longhand/inherited_text.mako.rs index f47178e9c76..e4079129328 100644 --- a/components/style/properties/longhand/inherited_text.mako.rs +++ b/components/style/properties/longhand/inherited_text.mako.rs @@ -1236,33 +1236,11 @@ ${helpers.predefined_type( complex_color=True, need_clone=True, spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color")} -<%helpers:longhand products="gecko" name="-webkit-text-stroke-width" animation_value_type="none" - spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width"> - use app_units::Au; - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - use values::specified::{BorderWidth, Length}; - - pub type SpecifiedValue = BorderWidth; - - #[inline] - pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { - BorderWidth::parse(context, input) - } - - pub mod computed_value { - use app_units::Au; - pub type T = Au; - } - #[inline] pub fn get_initial_value() -> computed_value::T { - Au::from_px(0) - } - #[inline] - pub fn get_initial_specified_value() -> SpecifiedValue { - BorderWidth::from_length(Length::zero()) - } - +${helpers.predefined_type("-webkit-text-stroke-width", "BorderWidth", "Au::from_px(0)", + initial_specified_value="specified::BorderWidth::from_length(specified::Length::zero())", + computed_type="::app_units::Au", products="gecko", + spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width", + animation_value_type="none")} // CSS Ruby Layout Module Level 1 diff --git a/components/style/properties/longhand/svg.mako.rs b/components/style/properties/longhand/svg.mako.rs index eb9f9156a42..7a764fbe9f5 100644 --- a/components/style/properties/longhand/svg.mako.rs +++ b/components/style/properties/longhand/svg.mako.rs @@ -58,34 +58,11 @@ ${helpers.single_keyword("mask-type", "luminance alpha", products="gecko", animation_value_type="none", spec="https://drafts.fxtf.org/css-masking/#propdef-mask-type")} -<%helpers:longhand name="clip-path" animation_value_type="none" products="gecko" boxed="True" - flags="CREATES_STACKING_CONTEXT" - spec="https://drafts.fxtf.org/css-masking/#propdef-clip-path"> - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - use values::specified::basic_shape::{ShapeSource, GeometryBox}; - - pub mod computed_value { - use app_units::Au; - use values::computed::basic_shape::{ShapeSource, GeometryBox}; - - pub type T = ShapeSource; - } - - pub type SpecifiedValue = ShapeSource; - - #[inline] - pub fn get_initial_value() -> computed_value::T { - Default::default() - } - - pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { - ShapeSource::parse(context, input) - } - - no_viewport_percentage!(SpecifiedValue); - +${helpers.predefined_type("clip-path", "basic_shape::ShapeWithGeometryBox", + "generics::basic_shape::ShapeSource::None", + products="gecko", boxed="True", + animation_value_type="none", flags="CREATES_STACKING_CONTEXT", + spec="https://drafts.fxtf.org/css-masking/#propdef-clip-path")} ${helpers.single_keyword("mask-mode", "match-source alpha luminance", diff --git a/components/style/properties/shorthand/border.mako.rs b/components/style/properties/shorthand/border.mako.rs index 35afe623205..00fc89b0cec 100644 --- a/components/style/properties/shorthand/border.mako.rs +++ b/components/style/properties/shorthand/border.mako.rs @@ -203,7 +203,8 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser) 'border-%s-radius' % (corner) for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left'] )}" extra_prefixes="webkit" spec="https://drafts.csswg.org/css-backgrounds/#border-radius"> - use values::specified::basic_shape::{BorderRadius, serialize_radius_values}; + use values::generics::serialize_radius_values; + use values::specified::basic_shape::BorderRadius; use parser::Parse; pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { diff --git a/components/style/properties/shorthand/outline.mako.rs b/components/style/properties/shorthand/outline.mako.rs index 30c585f65b5..2a1e7e54486 100644 --- a/components/style/properties/shorthand/outline.mako.rs +++ b/components/style/properties/shorthand/outline.mako.rs @@ -68,7 +68,7 @@ for corner in ['topleft', 'topright', 'bottomright', 'bottomleft'] )}" products="gecko" spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)"> use properties::shorthands; - use values::specified::basic_shape::serialize_radius_values; + use values::generics::serialize_radius_values; pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { // Re-use border-radius parsing. diff --git a/components/style/values/computed/basic_shape.rs b/components/style/values/computed/basic_shape.rs index 2ae1675bb3c..eb5d67a98db 100644 --- a/components/style/values/computed/basic_shape.rs +++ b/components/style/values/computed/basic_shape.rs @@ -9,45 +9,19 @@ use std::fmt; use style_traits::ToCss; -use values::computed::{BorderRadiusSize, LengthOrPercentage}; +use values::computed::LengthOrPercentage; use values::computed::position::Position; -use values::specified::url::SpecifiedUrl; +use values::generics::basic_shape::{BorderRadius as GenericBorderRadius, ShapeRadius as GenericShapeRadius}; +use values::generics::basic_shape::{InsetRect as GenericInsetRect, Polygon as GenericPolygon, ShapeSource}; -pub use values::specified::basic_shape::{self, FillRule, GeometryBox, ShapeBox}; +pub use values::generics::basic_shape::FillRule; +pub use values::specified::basic_shape::{self, GeometryBox, ShapeBox}; -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum ShapeSource { - Url(SpecifiedUrl), - Shape(BasicShape, Option), - Box(T), - None, -} - -impl Default for ShapeSource { - fn default() -> Self { - ShapeSource::None - } -} - -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 reference)) => { - try!(shape.to_css(dest)); - try!(dest.write_str(" ")); - reference.to_css(dest) - } - ShapeSource::Shape(ref shape, None) => shape.to_css(dest), - ShapeSource::Box(ref reference) => reference.to_css(dest), - ShapeSource::None => dest.write_str("none"), - - } - } -} +/// The computed value used by `clip-path` +pub type ShapeWithGeometryBox = ShapeSource; +/// The computed value used by `shape-outside` +pub type ShapeWithShapeBox = ShapeSource; #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -70,35 +44,8 @@ impl ToCss for BasicShape { } } -#[derive(Clone, PartialEq, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct InsetRect { - pub top: LengthOrPercentage, - pub right: LengthOrPercentage, - pub bottom: LengthOrPercentage, - pub left: LengthOrPercentage, - pub round: Option, -} - -impl ToCss for InsetRect { - // XXXManishearth again, we should try to reduce the number of values printed here - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("inset(")); - try!(self.top.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.right.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.bottom.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.left.to_css(dest)); - if let Some(ref radius) = self.round { - try!(dest.write_str(" round ")); - try!(radius.to_css(dest)); - } - dest.write_str(")") - } -} +/// The computed value of `inset()` +pub type InsetRect = GenericInsetRect; #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -140,77 +87,13 @@ impl ToCss for Ellipse { } } -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -/// https://drafts.csswg.org/css-shapes/#funcdef-polygon -pub struct Polygon { - pub fill: FillRule, - pub coordinates: Vec<(LengthOrPercentage, LengthOrPercentage)>, -} +/// The computed value of `Polygon` +pub type Polygon = GenericPolygon; -impl ToCss for Polygon { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("polygon(")); - let mut need_space = false; - if self.fill != Default::default() { - try!(self.fill.to_css(dest)); - try!(dest.write_str(", ")); - } - for coord in &self.coordinates { - if need_space { - try!(dest.write_str(", ")); - } - try!(coord.0.to_css(dest)); - try!(dest.write_str(" ")); - try!(coord.1.to_css(dest)); - need_space = true; - } - dest.write_str(")") - } -} +/// The computed value of `BorderRadius` +pub type BorderRadius = GenericBorderRadius; -/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius -#[derive(Clone, PartialEq, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum ShapeRadius { - Length(LengthOrPercentage), - ClosestSide, - FarthestSide, -} +/// The computed value of `ShapeRadius` +pub type ShapeRadius = GenericShapeRadius; -impl Default for ShapeRadius { - fn default() -> Self { - ShapeRadius::ClosestSide - } -} - -impl ToCss for ShapeRadius { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - ShapeRadius::Length(lop) => lop.to_css(dest), - ShapeRadius::ClosestSide => dest.write_str("closest-side"), - ShapeRadius::FarthestSide => dest.write_str("farthest-side"), - } - } -} - -/// https://drafts.csswg.org/css-backgrounds-3/#border-radius -#[derive(Clone, PartialEq, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct BorderRadius { - pub top_left: BorderRadiusSize, - pub top_right: BorderRadiusSize, - pub bottom_right: BorderRadiusSize, - pub bottom_left: BorderRadiusSize, -} - -impl ToCss for BorderRadius { - #[inline] - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - basic_shape::serialize_radius_values(dest, &self.top_left.0, &self.top_right.0, - &self.bottom_right.0, &self.bottom_left.0) - } -} +impl Copy for ShapeRadius {} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 474239c56f9..85a8ba19368 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -4,16 +4,18 @@ //! Computed values. -use app_units::Au; use euclid::size::Size2D; use font_metrics::FontMetricsProvider; use media_queries::Device; use properties::ComputedValues; use std::fmt; use style_traits::ToCss; -use super::{CSSFloat, CSSInteger, RGBA, specified}; +use super::{CSSFloat, CSSInteger, RGBA}; +use super::generics::BorderRadiusSize as GenericBorderRadiusSize; +use super::specified; use super::specified::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize}; +pub use app_units::Au; pub use cssparser::Color as CSSColor; pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientKind, Image, ImageRect}; pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword}; @@ -297,43 +299,19 @@ impl ComputedValueAsSpecified for specified::AlignJustifyContent {} impl ComputedValueAsSpecified for specified::AlignJustifySelf {} impl ComputedValueAsSpecified for specified::BorderStyle {} -#[derive(Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct BorderRadiusSize(pub Size2D); +/// The computed value of `BorderRadiusSize` +pub type BorderRadiusSize = GenericBorderRadiusSize; impl BorderRadiusSize { - #[allow(missing_docs)] + /// Create a null value. + #[inline] pub fn zero() -> BorderRadiusSize { - BorderRadiusSize(Size2D::new(LengthOrPercentage::Length(Au(0)), LengthOrPercentage::Length(Au(0)))) + let zero = LengthOrPercentage::zero(); + GenericBorderRadiusSize(Size2D::new(zero.clone(), zero)) } } -impl ToComputedValue for specified::BorderRadiusSize { - type ComputedValue = BorderRadiusSize; - - #[inline] - fn to_computed_value(&self, context: &Context) -> BorderRadiusSize { - let w = self.0.width.to_computed_value(context); - let h = self.0.height.to_computed_value(context); - BorderRadiusSize(Size2D::new(w, h)) - } - - #[inline] - fn from_computed_value(computed: &BorderRadiusSize) -> Self { - let w = ToComputedValue::from_computed_value(&computed.0.width); - let h = ToComputedValue::from_computed_value(&computed.0.height); - specified::BorderRadiusSize(Size2D::new(w, h)) - } -} - -impl ToCss for BorderRadiusSize { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(self.0.width.to_css(dest)); - try!(dest.write_str("/")); - self.0.height.to_css(dest) - } -} +impl Copy for BorderRadiusSize {} #[derive(Debug, PartialEq, Clone, Copy)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] diff --git a/components/style/values/computed/position.rs b/components/style/values/computed/position.rs index d2cc4b1e48f..d30e911f9fa 100644 --- a/components/style/values/computed/position.rs +++ b/components/style/values/computed/position.rs @@ -3,59 +3,79 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! CSS handling for the computed value of -//! [`position`][position]s +//! [`position`][position] values. //! //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position use std::fmt; use style_traits::ToCss; use values::computed::LengthOrPercentage; +use values::generics::position::{Position as GenericPosition, PositionWithKeyword}; +use values::generics::position::HorizontalPosition as GenericHorizontalPosition; +use values::generics::position::VerticalPosition as GenericVerticalPosition; -#[derive(Debug, Clone, PartialEq, Copy)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct Position { - pub horizontal: LengthOrPercentage, - pub vertical: LengthOrPercentage, +/// The computed value of a CSS `` +pub type Position = PositionWithKeyword; + +impl Copy for Position {} + +/// The computed value for `` values without a keyword. +pub type OriginPosition = GenericPosition; + +impl Copy for OriginPosition {} + +impl OriginPosition { + #[inline] + /// The initial value for `perspective-origin` + pub fn center() -> OriginPosition { + GenericPosition { + horizontal: LengthOrPercentage::Percentage(0.5), + vertical: LengthOrPercentage::Percentage(0.5), + } + } } impl Position { + #[inline] /// Construct a position at (0, 0) pub fn zero() -> Self { Position { - horizontal: LengthOrPercentage::zero(), - vertical: LengthOrPercentage::zero(), + horizontal: GenericHorizontalPosition(LengthOrPercentage::zero()), + vertical: GenericVerticalPosition(LengthOrPercentage::zero()), } } } impl ToCss for Position { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(self.horizontal.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.vertical.to_css(dest)); - Ok(()) + self.horizontal.to_css(dest)?; + dest.write_str(" ")?; + self.vertical.to_css(dest) } } -#[derive(Debug, Clone, PartialEq, Copy)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct HorizontalPosition(pub LengthOrPercentage); +/// The computed value of a horizontal `` +pub type HorizontalPosition = GenericHorizontalPosition; -impl ToCss for HorizontalPosition { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - self.0.to_css(dest) +impl Copy for HorizontalPosition {} + +impl HorizontalPosition { + #[inline] + /// Create a zero position value. + pub fn zero() -> HorizontalPosition { + GenericHorizontalPosition(LengthOrPercentage::Percentage(0.0)) } } -#[derive(Debug, Clone, PartialEq, Copy)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct VerticalPosition(pub LengthOrPercentage); +/// The computed value of a vertical `` +pub type VerticalPosition = GenericVerticalPosition; -impl ToCss for VerticalPosition { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - self.0.to_css(dest) +impl Copy for VerticalPosition {} + +impl VerticalPosition { + #[inline] + /// Create a zero position value. + pub fn zero() -> VerticalPosition { + GenericVerticalPosition(LengthOrPercentage::Percentage(0.0)) } } diff --git a/components/style/values/generics/basic_shape.rs b/components/style/values/generics/basic_shape.rs new file mode 100644 index 00000000000..d961141dbc9 --- /dev/null +++ b/components/style/values/generics/basic_shape.rs @@ -0,0 +1,397 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! 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::ToCss; +use values::HasViewportPercentage; +use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue}; +use values::generics::BorderRadiusSize; +use values::specified::url::SpecifiedUrl; + +/// A generic type used for `border-radius`, `outline-radius` and `inset()` values. +/// +/// https://drafts.csswg.org/css-backgrounds-3/#border-radius +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct BorderRadius { + /// The top left radius. + pub top_left: BorderRadiusSize, + /// The top right radius. + pub top_right: BorderRadiusSize, + /// The bottom right radius. + pub bottom_right: BorderRadiusSize, + /// The bottom left radius. + pub bottom_left: BorderRadiusSize, +} + +/// Serialization helper for types of longhands like `border-radius` and `outline-radius` +pub fn serialize_radius_values(dest: &mut W, top_left: &Size2D, + top_right: &Size2D, bottom_right: &Size2D, + bottom_left: &Size2D) -> fmt::Result + where L: ToCss + PartialEq, W: fmt::Write +{ + if top_left.width == top_left.height && top_right.width == top_right.height && + bottom_right.width == bottom_right.height && bottom_left.width == bottom_left.height { + serialize_four_sides(dest, &top_left.width, &top_right.width, + &bottom_right.width, &bottom_left.width) + } else { + serialize_four_sides(dest, &top_left.width, &top_right.width, + &bottom_right.width, &bottom_left.width)?; + dest.write_str(" / ")?; + serialize_four_sides(dest, &top_left.height, &top_right.height, + &bottom_right.height, &bottom_left.height) + } +} + +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) + } +} + +impl ToComputedValue for BorderRadius { + type ComputedValue = BorderRadius; + + #[inline] + fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { + BorderRadius { + top_left: self.top_left.to_computed_value(cx), + top_right: self.top_right.to_computed_value(cx), + bottom_right: self.bottom_right.to_computed_value(cx), + bottom_left: self.bottom_left.to_computed_value(cx), + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + BorderRadius { + top_left: ToComputedValue::from_computed_value(&computed.top_left), + top_right: ToComputedValue::from_computed_value(&computed.top_right), + bottom_right: ToComputedValue::from_computed_value(&computed.bottom_right), + bottom_left: ToComputedValue::from_computed_value(&computed.bottom_left), + } + } +} + +/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius +#[derive(Clone, PartialEq, Debug)] +#[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 } +} + +impl ToCss for ShapeRadius { + #[inline] + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + ShapeRadius::Length(ref lop) => lop.to_css(dest), + ShapeRadius::ClosestSide => dest.write_str("closest-side"), + ShapeRadius::FarthestSide => dest.write_str("farthest-side"), + } + } +} + +impl ToComputedValue for ShapeRadius { + type ComputedValue = ShapeRadius; + + #[inline] + fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { + match *self { + ShapeRadius::Length(ref lop) => ShapeRadius::Length(lop.to_computed_value(cx)), + ShapeRadius::ClosestSide => ShapeRadius::ClosestSide, + ShapeRadius::FarthestSide => ShapeRadius::FarthestSide, + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + match *computed { + ShapeRadius::Length(ref lop) => ShapeRadius::Length(ToComputedValue::from_computed_value(lop)), + ShapeRadius::ClosestSide => ShapeRadius::ClosestSide, + ShapeRadius::FarthestSide => ShapeRadius::FarthestSide, + } + } +} + +// 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, PartialEq, Debug)] +#[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(")?; + if self.fill != FillRule::default() { + self.fill.to_css(dest)?; + dest.write_str(", ")?; + } + + for (i, coord) in self.coordinates.iter().enumerate() { + if i > 0 { + dest.write_str(", ")?; + } + + coord.0.to_css(dest)?; + dest.write_str(" ")?; + coord.1.to_css(dest)?; + } + + dest.write_str(")") + } +} + +impl ToComputedValue for Polygon { + type ComputedValue = Polygon; + + #[inline] + fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { + Polygon { + fill: self.fill.to_computed_value(cx), + coordinates: self.coordinates.iter().map(|c| { + (c.0.to_computed_value(cx), c.1.to_computed_value(cx)) + }).collect(), + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Polygon { + fill: ToComputedValue::from_computed_value(&computed.fill), + coordinates: computed.coordinates.iter().map(|c| { + (ToComputedValue::from_computed_value(&c.0), + ToComputedValue::from_computed_value(&c.1)) + }).collect(), + } + } +} + +#[derive(Clone, PartialEq, Debug)] +#[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(")") + } +} + +impl ToComputedValue for InsetRect { + type ComputedValue = InsetRect; + + #[inline] + fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { + InsetRect { + top: self.top.to_computed_value(cx), + right: self.right.to_computed_value(cx), + bottom: self.bottom.to_computed_value(cx), + left: self.left.to_computed_value(cx), + round: self.round.as_ref().map(|r| r.to_computed_value(cx)), + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + InsetRect { + top: ToComputedValue::from_computed_value(&computed.top), + right: ToComputedValue::from_computed_value(&computed.right), + bottom: ToComputedValue::from_computed_value(&computed.bottom), + left: ToComputedValue::from_computed_value(&computed.left), + round: computed.round.as_ref().map(|r| ToComputedValue::from_computed_value(r)), + } + } +} + +/// A shape source, for some reference box +/// +/// `clip-path` uses ShapeSource, +/// `shape-outside` uses ShapeSource +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[allow(missing_docs)] +pub enum ShapeSource { + Url(SpecifiedUrl), + Shape(B, Option), + Box(T), + None, +} + +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 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 ToComputedValue for ShapeSource { + type ComputedValue = ShapeSource; + + #[inline] + fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { + match *self { + ShapeSource::Url(ref url) => ShapeSource::Url(url.to_computed_value(cx)), + ShapeSource::Shape(ref shape, ref ref_box) => { + ShapeSource::Shape(shape.to_computed_value(cx), + ref_box.as_ref().map(|ref val| val.to_computed_value(cx))) + }, + ShapeSource::Box(ref ref_box) => ShapeSource::Box(ref_box.to_computed_value(cx)), + ShapeSource::None => ShapeSource::None, + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + match *computed { + ShapeSource::Url(ref url) => ShapeSource::Url(SpecifiedUrl::from_computed_value(url)), + ShapeSource::Shape(ref shape, ref ref_box) => { + ShapeSource::Shape(ToComputedValue::from_computed_value(shape), + ref_box.as_ref().map(|val| ToComputedValue::from_computed_value(val))) + }, + ShapeSource::Box(ref ref_box) => ShapeSource::Box(ToComputedValue::from_computed_value(ref_box)), + ShapeSource::None => ShapeSource::None, + } + } +} diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs new file mode 100644 index 00000000000..c0a3823f3b0 --- /dev/null +++ b/components/style/values/generics/mod.rs @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Generic types that share their serialization implementations +//! for both specified and computed values. + +use euclid::size::Size2D; +use std::fmt; +use style_traits::ToCss; +use super::HasViewportPercentage; +use super::computed::{Context, ToComputedValue}; + +pub use self::basic_shape::serialize_radius_values; + +pub mod basic_shape; +pub mod position; + +#[derive(Clone, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +/// A type for representing CSS `widthh` and `height` values. +pub struct BorderRadiusSize(pub Size2D); + +impl HasViewportPercentage for BorderRadiusSize { + #[inline] + fn has_viewport_percentage(&self) -> bool { false } +} + +impl BorderRadiusSize { + #[inline] + /// Create a new `BorderRadiusSize` for an area of given width and height. + pub fn new(width: L, height: L) -> BorderRadiusSize { + BorderRadiusSize(Size2D::new(width, height)) + } +} + +impl BorderRadiusSize { + #[inline] + /// Create a new `BorderRadiusSize` for a circle of given radius. + pub fn circle(radius: L) -> BorderRadiusSize { + BorderRadiusSize(Size2D::new(radius.clone(), radius)) + } +} + +impl ToCss for BorderRadiusSize { + #[inline] + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + self.0.width.to_css(dest)?; + dest.write_str(" ")?; + self.0.height.to_css(dest) + } +} + +impl ToComputedValue for BorderRadiusSize { + type ComputedValue = BorderRadiusSize; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + let w = self.0.width.to_computed_value(context); + let h = self.0.height.to_computed_value(context); + BorderRadiusSize(Size2D::new(w, h)) + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + let w = ToComputedValue::from_computed_value(&computed.0.width); + let h = ToComputedValue::from_computed_value(&computed.0.height); + BorderRadiusSize(Size2D::new(w, h)) + } +} diff --git a/components/style/values/generics/position.rs b/components/style/values/generics/position.rs new file mode 100644 index 00000000000..d15198b638e --- /dev/null +++ b/components/style/values/generics/position.rs @@ -0,0 +1,302 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Generic types for CSS handling of specified and computed values of +//! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position) + +use cssparser::Parser; +use parser::{Parse, ParserContext}; +use std::fmt; +use style_traits::ToCss; +use values::HasViewportPercentage; +use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue}; +use values::specified::{LengthOrPercentage, Percentage}; + +define_css_keyword_enum!{ Keyword: + "center" => Center, + "left" => Left, + "right" => Right, + "top" => Top, + "bottom" => Bottom, + "x-start" => XStart, + "x-end" => XEnd, + "y-start" => YStart, + "y-end" => YEnd +} + +add_impls_for_keyword_enum!(Keyword); + +impl Keyword { + #[inline] + /// The defaults for position keywords are `left` and `top` (`x-start` and `y-start` for logical). + /// This method checks whether this keyword indicates their opposite sides. See the + /// `ToComputedValue` impl on `HorizontalPosition` and `VerticalPosition` for its use case. + pub fn is_other_side(&self) -> bool { + if self.is_horizontal() || self.is_logical_x() { + matches!(*self, Keyword::Right | Keyword::XEnd) + } else { + matches!(*self, Keyword::Bottom | Keyword::YEnd) + } + } + + #[inline] + /// Check whether this is a keyword for horizontal position. + pub fn is_horizontal(&self) -> bool { + matches!(*self, Keyword::Left | Keyword::Right) + } + + #[inline] + /// Check whether this is a keyword for vertical position. + pub fn is_vertical(&self) -> bool { + matches!(*self, Keyword::Top | Keyword::Bottom) + } + + #[inline] + /// Check whether this is a horizontal logical keyword. + pub fn is_logical_x(&self) -> bool { + matches!(*self, Keyword::XStart | Keyword::XEnd) + } + + #[inline] + /// Check whether this is a vertical logical keyword. + pub fn is_logical_y(&self) -> bool { + matches!(*self, Keyword::YStart | Keyword::YEnd) + } + + #[inline] + /// Check whether this is a logical keyword. + pub fn is_logical(&self) -> bool { + self.is_logical_x() || self.is_logical_y() + } +} + +impl From for LengthOrPercentage { + fn from(val: Keyword) -> LengthOrPercentage { + match val { + Keyword::Center => LengthOrPercentage::Percentage(Percentage(0.5)), + Keyword::Left | Keyword::Top => LengthOrPercentage::Percentage(Percentage(0.0)), + Keyword::Right | Keyword::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)), + // FIXME(canaltinova): Support logical keywords + Keyword::XStart | Keyword::YStart => LengthOrPercentage::Percentage(Percentage(0.0)), + Keyword::XEnd | Keyword::YEnd => LengthOrPercentage::Percentage(Percentage(1.0)), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +/// A generic type for representing horizontal or vertical `` value. +pub struct PositionValue { + /// Even though this is generic, it's always a `` value. + pub position: Option, + /// A position keyword. + pub keyword: Option, +} + +impl HasViewportPercentage for PositionValue { + #[inline] + fn has_viewport_percentage(&self) -> bool { + self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage()) + } +} + +impl PositionValue { + /// Internal parsing function which (after parsing) checks the keyword with the + /// given function. + pub fn parse_internal(context: &ParserContext, input: &mut Parser, + mut is_allowed_keyword: F) -> Result, ()> + where F: FnMut(Keyword) -> bool + { + let (mut pos, mut keyword) = (None, None); + for _ in 0..2 { + if let Ok(l) = input.try(|i| L::parse(context, i)) { + if pos.is_some() { + return Err(()) + } + + pos = Some(l); + } + + if let Ok(k) = input.try(Keyword::parse) { + if keyword.is_some() || !is_allowed_keyword(k) { + return Err(()) + } + + keyword = Some(k); + } + } + + if pos.is_some() { + if let Some(Keyword::Center) = keyword { + return Err(()) // "center" and is not allowed + } + } else if keyword.is_none() { + return Err(()) // at least one value is necessary + } + + Ok(PositionValue { + position: pos, + keyword: keyword, + }) + } +} + +impl ToCss for PositionValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if let Some(keyword) = self.keyword { + keyword.to_css(dest)?; + } + + if let Some(ref position) = self.position { + if self.keyword.is_some() { + dest.write_str(" ")?; + } + + position.to_css(dest)?; + } + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +/// A generic type for representing horizontal `` +pub struct HorizontalPosition(pub L); + +impl ToCss for HorizontalPosition { + #[inline] + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + self.0.to_css(dest) + } +} + +impl HasViewportPercentage for HorizontalPosition { + #[inline] + fn has_viewport_percentage(&self) -> bool { + self.0.has_viewport_percentage() + } +} + +impl Parse for HorizontalPosition> { + #[inline] + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + PositionValue::parse_internal(context, input, |keyword| { + matches!{ keyword, + Keyword::Left | Keyword::Right | Keyword::Center | + Keyword::XStart | Keyword::XEnd + } + }).map(HorizontalPosition) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +/// A generic type for representing vertical `` +pub struct VerticalPosition(pub L); + +impl ToCss for VerticalPosition { + #[inline] + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + self.0.to_css(dest) + } +} + +impl HasViewportPercentage for VerticalPosition { + #[inline] + fn has_viewport_percentage(&self) -> bool { + self.0.has_viewport_percentage() + } +} + +impl Parse for VerticalPosition> { + #[inline] + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + PositionValue::parse_internal(context, input, |keyword| { + matches!{ keyword, + Keyword::Top | Keyword::Bottom | Keyword::Center | + Keyword::YStart | Keyword::YEnd + } + }).map(VerticalPosition) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +/// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position). +/// +/// Note that the horizontal and vertical positions aren't really different types. +/// They're just unit struct wrappers over `LengthOrPercentage`. They should be different +/// because they allow different keywords (for e.g., vertical position doesn't allow +/// `right` or `left` keywords and vice versa). +pub struct Position { + /// The horizontal component of position. + pub horizontal: H, + /// The vertical component of position. + pub vertical: V, +} + +/// A generic type for representing positions with keywords. +pub type PositionWithKeyword = Position, VerticalPosition>; + +impl HasViewportPercentage for Position { + #[inline] + fn has_viewport_percentage(&self) -> bool { + self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage() + } +} + +impl ToCss for Position { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + self.horizontal.to_css(dest)?; + dest.write_str(" ")?; + self.vertical.to_css(dest) + } +} + +impl ToCss for PositionWithKeyword> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + macro_rules! to_css_with_keyword { + ($pos:expr, $default:expr) => { + $pos.keyword.unwrap_or($default).to_css(dest)?; + if let Some(ref position) = $pos.position { + dest.write_str(" ")?; + position.to_css(dest)?; + } + } + } + + if (self.horizontal.0.keyword.is_some() && self.horizontal.0.position.is_some()) || + (self.vertical.0.keyword.is_some() && self.vertical.0.position.is_some()) { + to_css_with_keyword!(self.horizontal.0, Keyword::Left); + dest.write_str(" ")?; + to_css_with_keyword!(self.vertical.0, Keyword::Top); + return Ok(()) + } + + self.horizontal.to_css(dest)?; + dest.write_str(" ")?; + self.vertical.to_css(dest) + } +} + +impl ToComputedValue for Position { + type ComputedValue = Position; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + Position { + horizontal: self.horizontal.to_computed_value(context), + vertical: self.vertical.to_computed_value(context), + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Position { + horizontal: ToComputedValue::from_computed_value(&computed.horizontal), + vertical: ToComputedValue::from_computed_value(&computed.vertical), + } + } +} diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 256d07dd733..2b371b74da9 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -82,6 +82,7 @@ macro_rules! add_impls_for_keyword_enum { } pub mod computed; +pub mod generics; pub mod specified; /// A CSS float value. diff --git a/components/style/values/specified/basic_shape.rs b/components/style/values/specified/basic_shape.rs index 311049043e0..9d1176dad70 100644 --- a/components/style/values/specified/basic_shape.rs +++ b/components/style/values/specified/basic_shape.rs @@ -8,129 +8,25 @@ //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape use cssparser::Parser; -use euclid::size::Size2D; use parser::{Parse, ParserContext}; -use properties::shorthands::{parse_four_sides, serialize_four_sides}; +use properties::shorthands::parse_four_sides; use std::ascii::AsciiExt; use std::fmt; use style_traits::ToCss; use values::HasViewportPercentage; use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue}; use values::computed::basic_shape as computed_basic_shape; -use values::specified::{BorderRadiusSize, LengthOrPercentage, Percentage}; +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}; use values::specified::position::{Keyword, Position}; -use values::specified::url::SpecifiedUrl; -/// A shape source, for some reference box -/// -/// clip-path uses ShapeSource, -/// shape-outside uses ShapeSource -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum ShapeSource { - Url(SpecifiedUrl), - Shape(BasicShape, Option), - Box(T), - None, -} +/// The specified value used by `clip-path` +pub type ShapeWithGeometryBox = ShapeSource; -impl Default for ShapeSource { - fn default() -> Self { - ShapeSource::None - } -} - -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 reference)) => { - try!(shape.to_css(dest)); - try!(dest.write_str(" ")); - reference.to_css(dest) - } - ShapeSource::Shape(ref shape, None) => shape.to_css(dest), - ShapeSource::Box(ref reference) => reference.to_css(dest), - ShapeSource::None => dest.write_str("none"), - - } - } -} - -impl Parse for ShapeSource { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - if input.try(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(ShapeSource::None) - } - - if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) { - 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 reference = None; - - while parse_component(context, input, &mut shape) || - parse_component(context, input, &mut reference) { - // - } - - if let Some(shp) = shape { - return Ok(ShapeSource::Shape(shp, reference)) - } - - match reference { - Some(r) => Ok(ShapeSource::Box(r)), - None => Err(()) - } - } -} - -impl ToComputedValue for ShapeSource { - type ComputedValue = computed_basic_shape::ShapeSource; - - #[inline] - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - match *self { - ShapeSource::Url(ref url) => computed_basic_shape::ShapeSource::Url(url.to_computed_value(cx)), - ShapeSource::Shape(ref shape, ref reference) => { - computed_basic_shape::ShapeSource::Shape( - shape.to_computed_value(cx), - reference.as_ref().map(|ref r| r.to_computed_value(cx))) - }, - ShapeSource::Box(ref reference) => - computed_basic_shape::ShapeSource::Box(reference.to_computed_value(cx)), - ShapeSource::None => computed_basic_shape::ShapeSource::None, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - match *computed { - computed_basic_shape::ShapeSource::Url(ref url) => - ShapeSource::Url(SpecifiedUrl::from_computed_value(url)), - computed_basic_shape::ShapeSource::Shape(ref shape, ref reference) => { - ShapeSource::Shape( - ToComputedValue::from_computed_value(shape), - reference.as_ref().map(|r| ToComputedValue::from_computed_value(r))) - } - computed_basic_shape::ShapeSource::Box(ref reference) => - ShapeSource::Box(ToComputedValue::from_computed_value(reference)), - computed_basic_shape::ShapeSource::None => ShapeSource::None, - } - } -} +/// The specified value used by `shape-outside` +pub type ShapeWithShapeBox = ShapeSource; #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -200,23 +96,14 @@ impl ToComputedValue for BasicShape { } } -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// https://drafts.csswg.org/css-shapes/#funcdef-inset -#[allow(missing_docs)] -pub struct InsetRect { - pub top: LengthOrPercentage, - pub right: LengthOrPercentage, - pub bottom: LengthOrPercentage, - pub left: LengthOrPercentage, - pub round: Option, -} +/// The specified value of `inset()` +pub type InsetRect = GenericInsetRect; impl InsetRect { - #[allow(missing_docs)] + /// Parse the inner function arguments of `inset()` pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { - let (t, r, b, l) = try!(parse_four_sides(input, |i| LengthOrPercentage::parse(context, i))); - let mut rect = InsetRect { + let (t, r, b, l) = parse_four_sides(input, |i| LengthOrPercentage::parse(context, i))?; + let mut rect = GenericInsetRect { top: t, right: r, bottom: b, @@ -236,58 +123,12 @@ 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| InsetRect::parse_function_arguments(context, i)), + input.parse_nested_block(|i| GenericInsetRect::parse_function_arguments(context, i)), _ => Err(()) } } } -impl ToCss for InsetRect { - // XXXManishearth again, we should try to reduce the number of values printed here - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("inset(")); - try!(self.top.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.right.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.bottom.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.left.to_css(dest)); - if let Some(ref radius) = self.round { - try!(dest.write_str(" round ")); - try!(radius.to_css(dest)); - } - - dest.write_str(")") - } -} - -impl ToComputedValue for InsetRect { - type ComputedValue = computed_basic_shape::InsetRect; - - #[inline] - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - computed_basic_shape::InsetRect { - top: self.top.to_computed_value(cx), - right: self.right.to_computed_value(cx), - bottom: self.bottom.to_computed_value(cx), - left: self.left.to_computed_value(cx), - round: self.round.as_ref().map(|r| r.to_computed_value(cx)), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - InsetRect { - top: ToComputedValue::from_computed_value(&computed.top), - right: ToComputedValue::from_computed_value(&computed.right), - bottom: ToComputedValue::from_computed_value(&computed.bottom), - left: ToComputedValue::from_computed_value(&computed.left), - round: computed.round.map(|ref r| ToComputedValue::from_computed_value(r)), - } - } -} - /// https://drafts.csswg.org/css-shapes/#basic-shape-serialization /// /// Positions get serialized differently with basic shapes. Keywords @@ -336,13 +177,11 @@ fn serialize_basicshape_position(position: &Position, dest: &mut W) -> fmt::R replace_with_percent(y).to_css(dest) } - match (position.horizontal.keyword, position.horizontal.position.clone(), - position.vertical.keyword, position.vertical.position.clone()) { + match (position.horizontal.0.keyword, position.horizontal.0.position.clone(), + position.vertical.0.keyword, position.vertical.0.position.clone()) { (Some(hk), None, Some(vk), None) => { // two keywords: serialize as two lengths - serialize_position_pair(hk.to_length_or_percentage(), - vk.to_length_or_percentage(), - dest) + serialize_position_pair(hk.into(), vk.into(), dest) } (None, Some(hp), None, Some(vp)) => { // two lengths: just serialize regularly @@ -410,7 +249,7 @@ impl Parse for Circle { impl ToCss for Circle { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(dest.write_str("circle(")); - if ShapeRadius::ClosestSide != self.radius { + if GenericShapeRadius::ClosestSide != self.radius { try!(self.radius.to_css(dest)); try!(dest.write_str(" ")); } @@ -484,7 +323,7 @@ impl Parse for Ellipse { 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.is_default() || !self.semiaxis_y.is_default() { + 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)); @@ -519,229 +358,41 @@ impl ToComputedValue for Ellipse { } } +/// The specified value of `Polygon` +pub type Polygon = GenericPolygon; -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// https://drafts.csswg.org/css-shapes/#funcdef-polygon -#[allow(missing_docs)] -pub struct Polygon { - pub fill: FillRule, - pub coordinates: Vec<(LengthOrPercentage, LengthOrPercentage)>, -} +/// The specified value of `ShapeRadius` +pub type ShapeRadius = GenericShapeRadius; -impl Polygon { - #[allow(missing_docs)] - pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result { - let fill = input.try(|input| { - let fill = FillRule::parse(input); - // only eat the comma if there is something before it - try!(input.expect_comma()); - fill - }).ok().unwrap_or_else(Default::default); - - let buf = try!(input.parse_comma_separated(|input| { - Ok((try!(LengthOrPercentage::parse(context, input)), - try!(LengthOrPercentage::parse(context, input)))) - })); - - Ok(Polygon { - fill: fill, - coordinates: buf, - }) - } -} - -impl Parse for Polygon { +impl Parse for ShapeRadius { fn parse(context: &ParserContext, input: &mut Parser) -> Result { - match input.try(|i| i.expect_function()) { - Ok(ref s) if s.eq_ignore_ascii_case("polygon") => - input.parse_nested_block(|i| Polygon::parse_function_arguments(context, i)), + 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(()) } } } -impl ToCss for Polygon { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("polygon(")); - let mut need_space = false; - if self.fill != Default::default() { - try!(self.fill.to_css(dest)); - try!(dest.write_str(", ")); - } - - for coord in &self.coordinates { - if need_space { - try!(dest.write_str(", ")); - } - - try!(coord.0.to_css(dest)); - try!(dest.write_str(" ")); - try!(coord.1.to_css(dest)); - need_space = true; - } - - dest.write_str(")") - } -} - -impl ToComputedValue for Polygon { - type ComputedValue = computed_basic_shape::Polygon; - - #[inline] - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - computed_basic_shape::Polygon { - fill: self.fill.to_computed_value(cx), - coordinates: self.coordinates.iter() - .map(|c| { - (c.0.to_computed_value(cx), - c.1.to_computed_value(cx)) - }).collect(), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Polygon { - fill: ToComputedValue::from_computed_value(&computed.fill), - coordinates: computed.coordinates.iter() - .map(|c| { - (ToComputedValue::from_computed_value(&c.0), - ToComputedValue::from_computed_value(&c.1)) - }).collect(), - } - } -} - -/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum ShapeRadius { - Length(LengthOrPercentage), - ClosestSide, - FarthestSide, -} - -impl ShapeRadius { - fn is_default(&self) -> bool { - *self == ShapeRadius::ClosestSide - } -} - -impl Default for ShapeRadius { - fn default() -> Self { - ShapeRadius::ClosestSide - } -} - -impl Parse for ShapeRadius { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - input.try(|i| LengthOrPercentage::parse_non_negative(context, i)).map(ShapeRadius::Length).or_else(|_| { - match_ignore_ascii_case! { &try!(input.expect_ident()), - "closest-side" => Ok(ShapeRadius::ClosestSide), - "farthest-side" => Ok(ShapeRadius::FarthestSide), - _ => Err(()) - } - }) - } -} - -impl ToCss for ShapeRadius { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - ShapeRadius::Length(ref lop) => lop.to_css(dest), - ShapeRadius::ClosestSide => dest.write_str("closest-side"), - ShapeRadius::FarthestSide => dest.write_str("farthest-side"), - } - } -} - - -impl ToComputedValue for ShapeRadius { - type ComputedValue = computed_basic_shape::ShapeRadius; - - #[inline] - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - match *self { - ShapeRadius::Length(ref lop) => - computed_basic_shape::ShapeRadius::Length(lop.to_computed_value(cx)), - ShapeRadius::ClosestSide => computed_basic_shape::ShapeRadius::ClosestSide, - ShapeRadius::FarthestSide => computed_basic_shape::ShapeRadius::FarthestSide, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - match *computed { - computed_basic_shape::ShapeRadius::Length(ref lop) => - ShapeRadius::Length(ToComputedValue::from_computed_value(lop)), - computed_basic_shape::ShapeRadius::ClosestSide => ShapeRadius::ClosestSide, - computed_basic_shape::ShapeRadius::FarthestSide => ShapeRadius::FarthestSide, - } - } -} - -/// https://drafts.csswg.org/css-backgrounds-3/#border-radius -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct BorderRadius { - pub top_left: BorderRadiusSize, - pub top_right: BorderRadiusSize, - pub bottom_right: BorderRadiusSize, - pub bottom_left: BorderRadiusSize, -} - -/// Serialization helper for types of longhands like `border-radius` and `outline-radius` -pub fn serialize_radius_values(dest: &mut W, top_left: &Size2D, - top_right: &Size2D, bottom_right: &Size2D, - bottom_left: &Size2D) -> fmt::Result - where L: ToCss + PartialEq, W: fmt::Write -{ - if top_left.width == top_left.height && - top_right.width == top_right.height && - bottom_right.width == bottom_right.height && - bottom_left.width == bottom_left.height { - serialize_four_sides(dest, - &top_left.width, - &top_right.width, - &bottom_right.width, - &bottom_left.width) - } else { - serialize_four_sides(dest, - &top_left.width, - &top_right.width, - &bottom_right.width, - &bottom_left.width)?; - dest.write_str(" / ")?; - serialize_four_sides(dest, - &top_left.height, - &top_right.height, - &bottom_right.height, - &bottom_left.height) - } -} - -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) - } -} +/// The specified value of `BorderRadius` +pub type BorderRadius = GenericBorderRadius; impl Parse for BorderRadius { fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let mut widths = try!(parse_one_set_of_border_values(context, input)); + let mut widths = parse_one_set_of_border_values(context, input)?; let mut heights = if input.try(|input| input.expect_delim('/')).is_ok() { - try!(parse_one_set_of_border_values(context, input)) + 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()), @@ -773,48 +424,6 @@ fn parse_one_set_of_border_values(context: &ParserContext, mut input: &mut Parse } } - -impl ToComputedValue for BorderRadius { - type ComputedValue = computed_basic_shape::BorderRadius; - - #[inline] - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - computed_basic_shape::BorderRadius { - top_left: self.top_left.to_computed_value(cx), - top_right: self.top_right.to_computed_value(cx), - bottom_right: self.bottom_right.to_computed_value(cx), - bottom_left: self.bottom_left.to_computed_value(cx), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - BorderRadius { - top_left: ToComputedValue::from_computed_value(&computed.top_left), - top_right: ToComputedValue::from_computed_value(&computed.top_right), - bottom_right: ToComputedValue::from_computed_value(&computed.bottom_right), - bottom_left: ToComputedValue::from_computed_value(&computed.bottom_left), - } - } -} - -// 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 { - fn default() -> Self { - FillRule::NonZero - } -} - /// https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 3181f5d67b6..ff8bdaa548e 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -1146,7 +1146,9 @@ impl ToCss for LengthOrPercentage { } } } + impl LengthOrPercentage { + #[inline] /// Returns a `zero` length. pub fn zero() -> LengthOrPercentage { LengthOrPercentage::Length(NoCalcLength::zero()) diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index e5d4693a2d3..b610401ac32 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -22,6 +22,7 @@ use style_traits::values::specified::AllowedNumericType; use super::{Auto, CSSFloat, CSSInteger, HasViewportPercentage, Either, None_}; use super::computed::{self, Context}; use super::computed::{Shadow as ComputedShadow, ToComputedValue}; +use super::generics::BorderRadiusSize as GenericBorderRadiusSize; #[cfg(feature = "gecko")] pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems}; @@ -282,30 +283,8 @@ pub fn parse_number_with_clamping_mode(context: &ParserContext, } } -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct BorderRadiusSize(pub Size2D); - -no_viewport_percentage!(BorderRadiusSize); - -impl BorderRadiusSize { - #[allow(missing_docs)] - pub fn zero() -> BorderRadiusSize { - let zero = LengthOrPercentage::Length(NoCalcLength::zero()); - BorderRadiusSize(Size2D::new(zero.clone(), zero)) - } - - #[allow(missing_docs)] - pub fn new(width: LengthOrPercentage, height: LengthOrPercentage) -> BorderRadiusSize { - BorderRadiusSize(Size2D::new(width, height)) - } - - #[allow(missing_docs)] - pub fn circle(radius: LengthOrPercentage) -> BorderRadiusSize { - BorderRadiusSize(Size2D::new(radius.clone(), radius)) - } -} +/// The specified value of `BorderRadiusSize` +pub type BorderRadiusSize = GenericBorderRadiusSize; impl Parse for BorderRadiusSize { #[inline] @@ -313,15 +292,7 @@ impl Parse for BorderRadiusSize { let first = try!(LengthOrPercentage::parse_non_negative(context, input)); let second = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) .unwrap_or_else(|()| first.clone()); - Ok(BorderRadiusSize(Size2D::new(first, second))) - } -} - -impl ToCss for BorderRadiusSize { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(self.0.width.to_css(dest)); - try!(dest.write_str(" ")); - self.0.height.to_css(dest) + Ok(GenericBorderRadiusSize(Size2D::new(first, second))) } } diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index edbe8c8472d..f8501e870c2 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -8,616 +8,256 @@ //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position use app_units::Au; -use cssparser::{Parser, Token}; +use cssparser::Parser; use parser::{Parse, ParserContext}; -use std::fmt; -use style_traits::ToCss; -use values::HasViewportPercentage; -use values::computed::{self, CalcLengthOrPercentage, Context}; +use properties::longhands::parse_origin; +use std::mem; +use values::Either; +use values::computed::{CalcLengthOrPercentage, Context}; use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, ToComputedValue}; use values::computed::position as computed_position; +use values::generics::position::{Position as GenericPosition, PositionValue, PositionWithKeyword}; +use values::generics::position::HorizontalPosition as GenericHorizontalPosition; +use values::generics::position::VerticalPosition as GenericVerticalPosition; use values::specified::{LengthOrPercentage, Percentage}; -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// A [position][pos]. -/// -/// [pos]: https://drafts.csswg.org/css-values/#position -pub struct Position { - /// The horizontal component. - pub horizontal: HorizontalPosition, - /// The vertical component. - pub vertical: VerticalPosition, -} +pub use values::generics::position::Keyword; -impl ToCss for Position { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - macro_rules! to_css_with_keyword { - ($pos:expr, $default_keyword:expr) => { - $pos.keyword.unwrap_or($default_keyword).to_css(dest)?; +/// The specified value of a CSS `` +pub type Position = PositionWithKeyword>; - if let Some(ref position) = $pos.position { - dest.write_str(" ")?; - position.to_css(dest)?; - }; - }; +/// The specified value for `` values without a keyword. +pub type OriginPosition = GenericPosition; + +impl Parse for OriginPosition { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + let result = parse_origin(context, input)?; + match result.depth { + Some(_) => Err(()), + None => Ok(GenericPosition { + horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), + vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), + }) } - - let mut is_keyword_needed = false; - let position_x = &self.horizontal; - let position_y = &self.vertical; - - if (position_x.keyword.is_some() && position_x.position.is_some()) || - (position_y.keyword.is_some() && position_y.position.is_some()) { - is_keyword_needed = true; - } - - if is_keyword_needed { - to_css_with_keyword!(position_x, Keyword::Left); - dest.write_str(" ")?; - to_css_with_keyword!(position_y, Keyword::Top); - } else { - position_x.to_css(dest)?; - dest.write_str(" ")?; - position_y.to_css(dest)?; - } - - Ok(()) } } -impl HasViewportPercentage for Position { - fn has_viewport_percentage(&self) -> bool { - self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage() - } -} +type PositionComponent = Either; impl Position { - /// Create a new position value. - pub fn new(mut first_position: Option, - mut second_position: Option, - first_keyword: Option, - second_keyword: Option) - -> Result { + /// Create a new position value from either a length or a keyword. + pub fn from_components(mut first_position: Option, + mut second_position: Option, + first_keyword: Option, + second_keyword: Option) -> Result { // Unwrap for checking if values are at right place. - let first_key = first_keyword.clone().unwrap_or(PositionComponent::Keyword(Keyword::Left)); - let second_key = second_keyword.clone().unwrap_or(PositionComponent::Keyword(Keyword::Top)); + let first_key = first_keyword.clone().unwrap_or(Either::Second(Keyword::Left)); + let second_key = second_keyword.clone().unwrap_or(Either::Second(Keyword::Top)); - // Check if position specified after center keyword. - if let PositionCategory::OtherKeyword = category(&first_key) { - if let Some(_) = first_position { - return Err(()); - }; - }; - if let PositionCategory::OtherKeyword = category(&second_key) { - if let Some(_) = second_position { - return Err(()); - }; - }; + let (horiz_keyword, vert_keyword) = match (&first_key, &second_key) { + // Check if a position is specified after center keyword. + (&Either::Second(Keyword::Center), _) if first_position.is_some() => return Err(()), + (_, &Either::Second(Keyword::Center)) if second_position.is_some() => return Err(()), - // Check first and second keywords for both 2 and 4 value positions. - let (horiz_keyword, vert_keyword) = match (category(&first_key), category(&second_key)) { - // Don't allow two vertical keywords or two horizontal keywords. - // also don't allow length/percentage values in the wrong position - (PositionCategory::HorizontalKeyword, PositionCategory::HorizontalKeyword) | - (PositionCategory::VerticalKeyword, PositionCategory::VerticalKeyword) | - (PositionCategory::LengthOrPercentage, PositionCategory::HorizontalKeyword) | - (PositionCategory::VerticalKeyword, PositionCategory::LengthOrPercentage) => return Err(()), + // Check first and second keywords for both 2 and 4 value positions. // FIXME(canaltinova): Allow logical keywords for Position. They are not in current spec yet. - (PositionCategory::HorizontalLogicalKeyword, _) | - (PositionCategory::VerticalLogicalKeyword, _) | - (_, PositionCategory::HorizontalLogicalKeyword) | - (_, PositionCategory::VerticalLogicalKeyword) => return Err(()), + (&Either::Second(k), _) if k.is_logical() => return Err(()), + (_, &Either::Second(k)) if k.is_logical() => return Err(()), + + // Don't allow two vertical keywords or two horizontal keywords. + (&Either::Second(k1), &Either::Second(k2)) + if (k1.is_horizontal() && k2.is_horizontal()) || (k1.is_vertical() && k2.is_vertical()) => + return Err(()), + + // Also don't allow values in the wrong position + (&Either::First(_), &Either::Second(k)) if k.is_horizontal() => return Err(()), + (&Either::Second(k), &Either::First(_)) if k.is_vertical() => return Err(()), // Swap if both are keywords and vertical precedes horizontal. - (PositionCategory::VerticalKeyword, PositionCategory::HorizontalKeyword) | - (PositionCategory::VerticalKeyword, PositionCategory::OtherKeyword) | - (PositionCategory::OtherKeyword, PositionCategory::HorizontalKeyword) => { - let tmp = first_position; - first_position = second_position; - second_position = tmp; - + (&Either::Second(k1), &Either::Second(k2)) + if (k1.is_vertical() && k2.is_horizontal()) || (k1.is_vertical() && k2 == Keyword::Center) || + (k1 == Keyword::Center && k2.is_horizontal()) => { + mem::swap(&mut first_position, &mut second_position); (second_keyword, first_keyword) }, + // By default, horizontal is first. _ => (first_keyword, second_keyword), }; - // Unwrap positions from PositionComponent and wrap with Option - let (first_position, second_position) = if let Some(PositionComponent::Length(horiz_pos)) = first_position { - if let Some(PositionComponent::Length(vert_pos)) = second_position { - (Some(horiz_pos), Some(vert_pos)) - } else { - (Some(horiz_pos), None) - } - } else { - if let Some(PositionComponent::Length(vert_pos)) = second_position { - (None, Some(vert_pos)) - } else { - (None, None) - } - }; + let (mut h_pos, mut h_key, mut v_pos, mut v_key) = (None, None, None, None); + if let Some(Either::First(l)) = first_position { + h_pos = Some(l); + } - // Unwrap keywords from PositionComponent and wrap with Option. - let (horizontal_keyword, vertical_keyword) = if let Some(PositionComponent::Keyword(horiz_key)) = - horiz_keyword { - if let Some(PositionComponent::Keyword(vert_key)) = vert_keyword { - (Some(horiz_key), Some(vert_key)) - } else { - (Some(horiz_key), None) - } - } else { - if let Some(PositionComponent::Keyword(vert_key)) = vert_keyword { - (None, Some(vert_key)) - } else { - (None, None) - } - }; + if let Some(Either::First(l)) = second_position { + v_pos = Some(l); + } + + if let Some(Either::Second(k)) = horiz_keyword { + h_key = Some(k); + } + + if let Some(Either::Second(k)) = vert_keyword { + v_key = Some(k); + } Ok(Position { - horizontal: HorizontalPosition { - keyword: horizontal_keyword, - position: first_position, - }, - vertical: VerticalPosition { - keyword: vertical_keyword, - position: second_position, - }, + horizontal: GenericHorizontalPosition(PositionValue { + keyword: h_key, + position: h_pos, + }), + vertical: GenericVerticalPosition(PositionValue { + keyword: v_key, + position: v_pos, + }), }) } /// Returns a "centered" position, as in "center center". pub fn center() -> Position { Position { - horizontal: HorizontalPosition { + horizontal: GenericHorizontalPosition(PositionValue { keyword: Some(Keyword::Center), position: None, - }, - vertical: VerticalPosition { + }), + vertical: GenericVerticalPosition(PositionValue { keyword: Some(Keyword::Center), position: None, - }, + }), } } } impl Parse for Position { fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let first = try!(PositionComponent::parse(context, input)); + let first = input.try(|i| PositionComponent::parse(context, i))?; let second = input.try(|i| PositionComponent::parse(context, i)) - .unwrap_or(PositionComponent::Keyword(Keyword::Center)); + .unwrap_or(Either::Second(Keyword::Center)); - // Try to parse third and fourth values if let Ok(third) = input.try(|i| PositionComponent::parse(context, i)) { + // There's a 3rd value. if let Ok(fourth) = input.try(|i| PositionComponent::parse(context, i)) { - // Handle 4 value background position - Position::new(Some(second), Some(fourth), Some(first), Some(third)) + // There's a 4th value. + Position::from_components(Some(second), Some(fourth), Some(first), Some(third)) } else { - // Handle 3 value background position there are several options: - if let PositionCategory::LengthOrPercentage = category(&first) { - Err(()) - } else { - if let PositionCategory::LengthOrPercentage = category(&second) { - if let PositionCategory::LengthOrPercentage = category(&third) { - Err(()) - } else { - // "keyword length keyword" - Position::new(Some(second), None, Some(first), Some(third)) - } - } else { - // "keyword keyword length" - Position::new(None, Some(third), Some(first), Some(second)) - } + // For 3 value background position, there are several options. + if let Either::First(_) = first { + return Err(()) // must be preceded by + } + + // only 3 values. + match (&second, &third) { + (&Either::First(_), &Either::First(_)) => Err(()), + // "keyword length keyword" + (&Either::First(_), _) => Position::from_components(Some(second), None, + Some(first), Some(third)), + // "keyword keyword length" + _ => Position::from_components(None, Some(third), Some(first), Some(second)), } } } else { - // Handle 2 value background position. - if let PositionCategory::LengthOrPercentage = category(&first) { - if let PositionCategory::LengthOrPercentage = category(&second) { - Position::new(Some(first), Some(second), None, None) - } else { - Position::new(Some(first), None, None, Some(second)) - } - } else { - if let PositionCategory::LengthOrPercentage = category(&second) { - Position::new(None, Some(second), Some(first), None) - } else { - Position::new(None, None, Some(first), Some(second)) - } + // only 2 values. + match (&first, &second) { + (&Either::First(_), &Either::First(_)) => + Position::from_components(Some(first), Some(second), None, None), + (&Either::First(_), &Either::Second(_)) => + Position::from_components(Some(first), None, None, Some(second)), + (&Either::Second(_), &Either::First(_)) => + Position::from_components(None, Some(second), Some(first), None), + (&Either::Second(_), &Either::Second(_)) => + Position::from_components(None, None, Some(first), Some(second)), } } } } -impl ToComputedValue for Position { - type ComputedValue = computed_position::Position; +impl PositionValue { + /// Generic function for the computed value of a position. + fn computed_value(&self, context: &Context) -> ComputedLengthOrPercentage { + match self.keyword { + Some(Keyword::Center) => ComputedLengthOrPercentage::Percentage(0.5), + Some(k) if k.is_other_side() => match self.position { + Some(ref x) => { + let (length, percentage) = match *x { + LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)), + LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)), + _ => (Au(0), None), + }; - #[inline] - fn to_computed_value(&self, context: &Context) -> computed_position::Position { - computed_position::Position { - horizontal: self.horizontal.to_computed_value(context).0, - vertical: self.vertical.to_computed_value(context).0, - } - } - - #[inline] - fn from_computed_value(computed: &computed_position::Position) -> Position { - Position { - horizontal: HorizontalPosition { - keyword: None, - position: Some(ToComputedValue::from_computed_value(&computed.horizontal)), - }, - vertical: VerticalPosition { - keyword: None, - position: Some(ToComputedValue::from_computed_value(&computed.vertical)), + ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage { + length: length, + percentage: percentage + }) + }, + None => ComputedLengthOrPercentage::Percentage(1.0), }, + _ => self.position.as_ref().map(|l| l.to_computed_value(context)) + .unwrap_or(ComputedLengthOrPercentage::Percentage(0.0)), } } } -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct HorizontalPosition { - pub keyword: Option, - pub position: Option, -} - -impl HasViewportPercentage for HorizontalPosition { - fn has_viewport_percentage(&self) -> bool { - self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage()) - } -} - -impl ToCss for HorizontalPosition { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - let mut keyword_present = false; - if let Some(keyword) = self.keyword { - try!(keyword.to_css(dest)); - keyword_present = true; - }; - - if let Some(ref position) = self.position { - if keyword_present { - try!(dest.write_str(" ")); - } - try!(position.to_css(dest)); - }; - Ok(()) - } -} - -impl Parse for HorizontalPosition { - #[inline] - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let first = try!(PositionComponent::parse(context, input)); - let second = input.try(|i| PositionComponent::parse(context, i)).ok(); - - let (keyword, position) = if let PositionCategory::LengthOrPercentage = category(&first) { - // "length keyword?" - (second, Some(first)) - } else { - // "keyword length?" - (Some(first), second) - }; - - // Unwrapping and checking keyword. - let keyword = match keyword { - Some(PositionComponent::Keyword(key)) => { - match category(keyword.as_ref().unwrap()) { - PositionCategory::VerticalKeyword | - PositionCategory::VerticalLogicalKeyword => return Err(()), - _ => Some(key), - } - }, - Some(_) => return Err(()), - None => None, - }; - - // Unwrapping and checking position. - let position = match position { - Some(PositionComponent::Length(pos)) => { - // "center " is not allowed - if let Some(Keyword::Center) = keyword { - return Err(()); - } - Some(pos) - }, - Some(_) => return Err(()), - None => None, - }; - - Ok(HorizontalPosition { - keyword: keyword, - position: position, - }) - } -} +/// The specified value of horizontal `` +pub type HorizontalPosition = GenericHorizontalPosition>; impl ToComputedValue for HorizontalPosition { type ComputedValue = computed_position::HorizontalPosition; #[inline] fn to_computed_value(&self, context: &Context) -> computed_position::HorizontalPosition { - let keyword = self.keyword.unwrap_or(Keyword::Left); - - // Construct horizontal computed LengthOrPercentage - let horizontal = match keyword { - // FIXME(canaltinova): Support logical keywords. - Keyword::Right | Keyword::XEnd => { - if let Some(ref x) = self.position { - let (length, percentage) = match *x { - LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)), - LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)), - _ => (Au(0), None), - }; - ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage { - length: length, - percentage: percentage - }) - } else { - ComputedLengthOrPercentage::Percentage(1.0) - } - }, - Keyword::Center => { - keyword.to_length_or_percentage().to_computed_value(context) - }, - _ => { - let zero = LengthOrPercentage::Percentage(Percentage(0.0)); - let horiz = self.position.as_ref().unwrap_or(&zero); - horiz.to_computed_value(context) - }, - }; - - computed_position::HorizontalPosition(horizontal) + GenericHorizontalPosition(self.0.computed_value(context)) } #[inline] fn from_computed_value(computed: &computed_position::HorizontalPosition) -> HorizontalPosition { - HorizontalPosition { + GenericHorizontalPosition(PositionValue { keyword: None, position: Some(ToComputedValue::from_computed_value(&computed.0)), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct VerticalPosition { - pub keyword: Option, - pub position: Option, -} - -impl HasViewportPercentage for VerticalPosition { - fn has_viewport_percentage(&self) -> bool { - self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage()) - } -} - -impl ToCss for VerticalPosition { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - let mut keyword_present = false; - if let Some(keyword) = self.keyword { - try!(keyword.to_css(dest)); - keyword_present = true; - }; - - if let Some(ref position) = self.position { - if keyword_present { - try!(dest.write_str(" ")); - } - try!(position.to_css(dest)); - }; - Ok(()) - } - -} - -impl Parse for VerticalPosition { - #[inline] - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let first = try!(PositionComponent::parse(context, input)); - let second = input.try(|i| PositionComponent::parse(context, i)).ok(); - - let (keyword, position) = if let PositionCategory::LengthOrPercentage = category(&first) { - // "length keyword?" - (second, Some(first)) - } else { - // "keyword length?" - (Some(first), second) - }; - - // Unwrapping and checking keyword. - let keyword = match keyword { - Some(PositionComponent::Keyword(key)) => { - match category(keyword.as_ref().unwrap()) { - PositionCategory::HorizontalKeyword | - PositionCategory::HorizontalLogicalKeyword => return Err(()), - _ => Some(key), - } - }, - Some(_) => return Err(()), - None => None, - }; - - // Unwrapping and checking position. - let position = match position { - Some(PositionComponent::Length(pos)) => { - // "center " is not allowed - if let Some(Keyword::Center) = keyword { - return Err(()); - } - Some(pos) - }, - Some(_) => return Err(()), - None => None, - }; - - Ok(VerticalPosition { - keyword: keyword, - position: position, }) } } +impl HorizontalPosition { + #[inline] + /// Initial specified value for vertical position (`top` keyword). + pub fn left() -> HorizontalPosition { + GenericHorizontalPosition(PositionValue { + keyword: Some(Keyword::Left), + position: None, + }) + } +} + + +/// The specified value of vertical `` +pub type VerticalPosition = GenericVerticalPosition>; + impl ToComputedValue for VerticalPosition { type ComputedValue = computed_position::VerticalPosition; #[inline] fn to_computed_value(&self, context: &Context) -> computed_position::VerticalPosition { - let keyword = self.keyword.unwrap_or(Keyword::Left); - - // Construct vertical computed LengthOrPercentage - let vertical = match keyword { - // FIXME(canaltinova): Support logical keywords. - Keyword::Bottom | Keyword::YEnd => { - if let Some(ref x) = self.position { - let (length, percentage) = match *x { - LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)), - LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)), - _ => (Au(0), None), - }; - ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage { - length: length, - percentage: percentage - }) - } else { - ComputedLengthOrPercentage::Percentage(1.0) - } - }, - Keyword::Center => { - keyword.to_length_or_percentage().to_computed_value(context) - }, - _ => { - let zero = LengthOrPercentage::Percentage(Percentage(0.0)); - let vert = self.position.as_ref().unwrap_or(&zero); - vert.to_computed_value(context) - }, - }; - - computed_position::VerticalPosition(vertical) + GenericVerticalPosition(self.0.computed_value(context)) } #[inline] fn from_computed_value(computed: &computed_position::VerticalPosition) -> VerticalPosition { - VerticalPosition { + GenericVerticalPosition(PositionValue { keyword: None, position: Some(ToComputedValue::from_computed_value(&computed.0)), - } + }) } } -define_css_keyword_enum!(Keyword: - "center" => Center, - "left" => Left, - "right" => Right, - "top" => Top, - "bottom" => Bottom, - "x-start" => XStart, - "x-end" => XEnd, - "y-start" => YStart, - "y-end" => YEnd); - -impl Keyword { - /// Convert the given keyword to a length or a percentage. - pub fn to_length_or_percentage(self) -> LengthOrPercentage { - match self { - Keyword::Center => LengthOrPercentage::Percentage(Percentage(0.5)), - Keyword::Left | Keyword::Top => LengthOrPercentage::Percentage(Percentage(0.0)), - Keyword::Right | Keyword::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)), - // FIXME(canaltinova): Support logical keywords - Keyword::XStart | Keyword::YStart => LengthOrPercentage::Percentage(Percentage(0.0)), - Keyword::XEnd | Keyword::YEnd => LengthOrPercentage::Percentage(Percentage(1.0)), - } - } -} - -// Collapse `Position` into a few categories to simplify the above `match` expression. -enum PositionCategory { - HorizontalKeyword, - VerticalKeyword, - HorizontalLogicalKeyword, - VerticalLogicalKeyword, - OtherKeyword, - LengthOrPercentage, -} - -/// A position component. -/// -/// http://dev.w3.org/csswg/css2/colors.html#propdef-background-position -#[derive(Clone, PartialEq)] -pub enum PositionComponent { - /// A `` - Length(LengthOrPercentage), - /// A position keyword. - Keyword(Keyword), -} - -fn category(p: &PositionComponent) -> PositionCategory { - if let PositionComponent::Keyword(keyword) = *p { - match keyword { - Keyword::Left | Keyword::Right => - PositionCategory::HorizontalKeyword, - Keyword::Top | Keyword::Bottom => - PositionCategory::VerticalKeyword, - Keyword::XStart | Keyword::XEnd => - PositionCategory::HorizontalLogicalKeyword, - Keyword::YStart | Keyword::YEnd => - PositionCategory::VerticalLogicalKeyword, - Keyword::Center => - PositionCategory::OtherKeyword, - } - } else { - PositionCategory::LengthOrPercentage - } -} - -impl HasViewportPercentage for PositionComponent { - fn has_viewport_percentage(&self) -> bool { - match *self { - PositionComponent::Length(ref length) => length.has_viewport_percentage(), - _ => false - } - } -} - -impl PositionComponent { - /// Convert the given position component to a length or a percentage. +impl VerticalPosition { #[inline] - pub fn to_length_or_percentage_computed(&self, cx: &Context) -> computed::LengthOrPercentage { - match *self { - PositionComponent::Length(ref value) => value.to_computed_value(cx), - PositionComponent::Keyword(keyword) => { - keyword.to_length_or_percentage().to_computed_value(cx) - } - } - } -} - -impl Parse for PositionComponent { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - input.try(|i| LengthOrPercentage::parse(context, i)) - .map(PositionComponent::Length) - .or_else(|()| { - match try!(input.next()) { - Token::Ident(value) => { - match_ignore_ascii_case! { &value, - "center" => Ok(PositionComponent::Keyword(Keyword::Center)), - "left" => Ok(PositionComponent::Keyword(Keyword::Left)), - "right" => Ok(PositionComponent::Keyword(Keyword::Right)), - "top" => Ok(PositionComponent::Keyword(Keyword::Top)), - "bottom" => Ok(PositionComponent::Keyword(Keyword::Bottom)), - "x-start" => Ok(PositionComponent::Keyword(Keyword::XStart)), - "x-end" => Ok(PositionComponent::Keyword(Keyword::XEnd)), - "y-start" => Ok(PositionComponent::Keyword(Keyword::YStart)), - "y-end" => Ok(PositionComponent::Keyword(Keyword::YEnd)), - _ => Err(()) - } - }, - _ => Err(()) - } - }) + /// Initial specified value for vertical position (`top` keyword). + pub fn top() -> VerticalPosition { + GenericVerticalPosition(PositionValue { + keyword: Some(Keyword::Top), + position: None, + }) } } diff --git a/tests/unit/style/properties/serialization.rs b/tests/unit/style/properties/serialization.rs index 7e862e51204..95c3ea638cf 100644 --- a/tests/unit/style/properties/serialization.rs +++ b/tests/unit/style/properties/serialization.rs @@ -799,8 +799,8 @@ mod shorthand_serialization { use style::properties::longhands::mask_position_y as position_y; use style::properties::longhands::mask_repeat as repeat; use style::properties::longhands::mask_size as size; + use style::values::generics::position::{HorizontalPosition, Keyword, PositionValue, VerticalPosition}; use style::values::specified::Image; - use style::values::specified::position::{HorizontalPosition, VerticalPosition, Keyword}; use super::*; macro_rules! single_vec_value_typedef { @@ -836,16 +836,16 @@ mod shorthand_serialization { let mode = single_vec_keyword_value!(mode, luminance); let position_x = single_vec_value_typedef!(position_x, - HorizontalPosition { + HorizontalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))), - } + }) ); let position_y = single_vec_value_typedef!(position_y, - VerticalPosition { + VerticalPosition(PositionValue { keyword: Some(Keyword::Bottom), position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))), - } + }) ); let size = single_vec_variant_value!(size, @@ -891,17 +891,17 @@ mod shorthand_serialization { let mode = single_vec_keyword_value!(mode, luminance); let position_x = single_vec_value_typedef!(position_x, - HorizontalPosition { + HorizontalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))), - } + }) ); let position_y = single_vec_value_typedef!(position_y, - VerticalPosition { + VerticalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))), - } + }) ); let size = single_vec_variant_value!(size,