Refactor basic shapes

This commit is contained in:
Anthony Ramine 2017-05-21 16:47:13 +02:00
parent 8cd4330b2a
commit a65c874ec8
7 changed files with 510 additions and 618 deletions

View file

@ -5,31 +5,217 @@
//! CSS handling for the [`basic-shape`](https://drafts.csswg.org/css-shapes/#typedef-basic-shape)
//! types that are generic over their `ToCss` implementations.
use cssparser::Parser;
use euclid::size::Size2D;
use parser::{Parse, ParserContext};
use properties::shorthands::serialize_four_sides;
use std::ascii::AsciiExt;
use std::fmt;
use style_traits::{HasViewportPercentage, ToCss};
use values::computed::ComputedValueAsSpecified;
use values::generics::BorderRadiusSize;
use values::generics::position::Position;
use values::specified::url::SpecifiedUrl;
/// A clipping shape, for `clip-path`.
pub type ClippingShape<BasicShape> = ShapeSource<BasicShape, GeometryBox>;
/// https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum GeometryBox {
FillBox,
StrokeBox,
ViewBox,
ShapeBox(ShapeBox),
}
impl ComputedValueAsSpecified for GeometryBox {}
/// A float area shape, for `shape-outside`.
pub type FloatAreaShape<BasicShape> = ShapeSource<BasicShape, ShapeBox>;
// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box
define_css_keyword_enum!(ShapeBox:
"margin-box" => MarginBox,
"border-box" => BorderBox,
"padding-box" => PaddingBox,
"content-box" => ContentBox
);
add_impls_for_keyword_enum!(ShapeBox);
/// A shape source, for some reference box.
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
pub enum ShapeSource<BasicShape, ReferenceBox> {
Url(SpecifiedUrl),
Shape(BasicShape, Option<ReferenceBox>),
Box(ReferenceBox),
None,
}
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
pub enum BasicShape<H, V, LengthOrPercentage> {
Inset(InsetRect<LengthOrPercentage>),
Circle(Circle<H, V, LengthOrPercentage>),
Ellipse(Ellipse<H, V, LengthOrPercentage>),
Polygon(Polygon<LengthOrPercentage>),
}
/// https://drafts.csswg.org/css-shapes/#funcdef-inset
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
pub struct InsetRect<LengthOrPercentage> {
pub top: LengthOrPercentage,
pub right: LengthOrPercentage,
pub bottom: LengthOrPercentage,
pub left: LengthOrPercentage,
pub round: Option<BorderRadius<LengthOrPercentage>>,
}
/// A generic type used for `border-radius`, `outline-radius` and `inset()` values.
///
/// https://drafts.csswg.org/css-backgrounds-3/#border-radius
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct BorderRadius<L> {
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
pub struct BorderRadius<LengthOrPercentage> {
/// The top left radius.
pub top_left: BorderRadiusSize<L>,
pub top_left: BorderRadiusSize<LengthOrPercentage>,
/// The top right radius.
pub top_right: BorderRadiusSize<L>,
pub top_right: BorderRadiusSize<LengthOrPercentage>,
/// The bottom right radius.
pub bottom_right: BorderRadiusSize<L>,
pub bottom_right: BorderRadiusSize<LengthOrPercentage>,
/// The bottom left radius.
pub bottom_left: BorderRadiusSize<L>,
pub bottom_left: BorderRadiusSize<LengthOrPercentage>,
}
/// https://drafts.csswg.org/css-shapes/#funcdef-circle
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue)]
pub struct Circle<H, V, LengthOrPercentage> {
pub position: Position<H, V>,
pub radius: ShapeRadius<LengthOrPercentage>,
}
/// https://drafts.csswg.org/css-shapes/#funcdef-ellipse
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue)]
pub struct Ellipse<H, V, LengthOrPercentage> {
pub position: Position<H, V>,
pub semiaxis_x: ShapeRadius<LengthOrPercentage>,
pub semiaxis_y: ShapeRadius<LengthOrPercentage>,
}
/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue)]
pub enum ShapeRadius<LengthOrPercentage> {
Length(LengthOrPercentage),
ClosestSide,
FarthestSide,
}
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
/// A generic type for representing the `polygon()` function
///
/// https://drafts.csswg.org/css-shapes/#funcdef-polygon
pub struct Polygon<LengthOrPercentage> {
/// The filling rule for a polygon.
pub fill: FillRule,
/// A collection of (x, y) coordinates to draw the polygon.
pub coordinates: Vec<(LengthOrPercentage, LengthOrPercentage)>,
}
// https://drafts.csswg.org/css-shapes/#typedef-fill-rule
// NOTE: Basic shapes spec says that these are the only two values, however
// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
// says that it can also be `inherit`
define_css_keyword_enum!(FillRule:
"nonzero" => NonZero,
"evenodd" => EvenOdd
);
add_impls_for_keyword_enum!(FillRule);
impl<B, T> HasViewportPercentage for ShapeSource<B, T> {
#[inline]
fn has_viewport_percentage(&self) -> bool { false }
}
impl<B: ToCss, T: ToCss> ToCss for ShapeSource<B, T> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
ShapeSource::Url(ref url) => url.to_css(dest),
ShapeSource::Shape(ref shape, Some(ref ref_box)) => {
shape.to_css(dest)?;
dest.write_str(" ")?;
ref_box.to_css(dest)
},
ShapeSource::Shape(ref shape, None) => shape.to_css(dest),
ShapeSource::Box(ref val) => val.to_css(dest),
ShapeSource::None => dest.write_str("none"),
}
}
}
impl ToCss for GeometryBox {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
GeometryBox::FillBox => dest.write_str("fill-box"),
GeometryBox::StrokeBox => dest.write_str("stroke-box"),
GeometryBox::ViewBox => dest.write_str("view-box"),
GeometryBox::ShapeBox(s) => s.to_css(dest),
}
}
}
impl<H, V, L> ToCss for BasicShape<H, V, L>
where H: ToCss,
V: ToCss,
L: PartialEq + ToCss,
Circle<H, V, L>: ToCss,
Ellipse<H, V, L>: ToCss,
{
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
BasicShape::Inset(ref rect) => rect.to_css(dest),
BasicShape::Circle(ref circle) => circle.to_css(dest),
BasicShape::Ellipse(ref ellipse) => ellipse.to_css(dest),
BasicShape::Polygon(ref polygon) => polygon.to_css(dest),
}
}
}
impl<L: ToCss + PartialEq> ToCss for InsetRect<L> {
// XXXManishearth We should try to reduce the number of values printed here
fn to_css<W>(&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<L: ToCss + PartialEq> ToCss for BorderRadius<L> {
#[inline]
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
serialize_radius_values(dest, &self.top_left.0, &self.top_right.0,
&self.bottom_right.0, &self.bottom_left.0)
}
}
/// Serialization helper for types of longhands like `border-radius` and `outline-radius`
@ -51,24 +237,6 @@ pub fn serialize_radius_values<L, W>(dest: &mut W, top_left: &Size2D<L>,
}
}
impl<L: ToCss + PartialEq> ToCss for BorderRadius<L> {
#[inline]
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
serialize_radius_values(dest, &self.top_left.0, &self.top_right.0,
&self.bottom_right.0, &self.bottom_left.0)
}
}
/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum ShapeRadius<L> {
Length(L),
ClosestSide,
FarthestSide,
}
impl<L> Default for ShapeRadius<L> {
#[inline]
fn default() -> Self { ShapeRadius::ClosestSide }
@ -85,64 +253,6 @@ impl<L: ToCss> ToCss for ShapeRadius<L> {
}
}
// https://drafts.csswg.org/css-shapes/#typedef-fill-rule
// NOTE: Basic shapes spec says that these are the only two values, however
// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
// says that it can also be `inherit`
define_css_keyword_enum!(FillRule:
"nonzero" => NonZero,
"evenodd" => EvenOdd
);
impl ComputedValueAsSpecified for FillRule {}
impl Default for FillRule {
#[inline]
fn default() -> Self { FillRule::NonZero }
}
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// A generic type for representing the `polygon()` function
///
/// https://drafts.csswg.org/css-shapes/#funcdef-polygon
pub struct Polygon<L> {
/// 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<L: Parse> Polygon<L> {
/// Parse the inner arguments of a `polygon` function.
pub fn parse_function_arguments(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
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<L: Parse> Parse for Polygon<L> {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
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<L: ToCss> ToCss for Polygon<L> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
dest.write_str("polygon(")?;
@ -165,105 +275,7 @@ impl<L: ToCss> ToCss for Polygon<L> {
}
}
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// https://drafts.csswg.org/css-shapes/#funcdef-inset
#[allow(missing_docs)]
pub struct InsetRect<L> {
pub top: L,
pub right: L,
pub bottom: L,
pub left: L,
pub round: Option<BorderRadius<L>>,
}
impl<L: ToCss + PartialEq> ToCss for InsetRect<L> {
// XXXManishearth We should try to reduce the number of values printed here
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
dest.write_str("inset(")?;
self.top.to_css(dest)?;
dest.write_str(" ")?;
self.right.to_css(dest)?;
dest.write_str(" ")?;
self.bottom.to_css(dest)?;
dest.write_str(" ")?;
self.left.to_css(dest)?;
if let Some(ref radius) = self.round {
dest.write_str(" round ")?;
radius.to_css(dest)?;
}
dest.write_str(")")
}
}
/// A shape source, for some reference box
///
/// `clip-path` uses ShapeSource<BasicShape, GeometryBox>,
/// `shape-outside` uses ShapeSource<BasicShape, ShapeBox>
#[derive(Clone, Debug, PartialEq, ToComputedValue)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum ShapeSource<B, T> {
Url(SpecifiedUrl),
Shape(B, Option<T>),
Box(T),
None,
}
impl<B, T> HasViewportPercentage for ShapeSource<B, T> {
impl Default for FillRule {
#[inline]
fn has_viewport_percentage(&self) -> bool { false }
}
impl<B: ToCss, T: ToCss> ToCss for ShapeSource<B, T> {
fn to_css<W>(&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<B: Parse, T: Parse> Parse for ShapeSource<B, T> {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
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<U: Parse>(context: &ParserContext, input: &mut Parser,
component: &mut Option<U>) -> bool {
if component.is_some() {
return false // already parsed this component
}
*component = input.try(|i| U::parse(context, i)).ok();
component.is_some()
}
let mut shape = None;
let mut ref_box = None;
while parse_component(context, input, &mut shape) ||
parse_component(context, input, &mut ref_box) {
//
}
if let Some(shp) = shape {
return Ok(ShapeSource::Shape(shp, ref_box))
}
ref_box.map(|v| ShapeSource::Box(v)).ok_or(())
}
fn default() -> Self { FillRule::NonZero }
}