servo/components/style/values/specified/basic_shape.rs
2016-08-19 23:36:37 +05:30

702 lines
23 KiB
Rust

/* 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 specified value of
//! [`basic-shape`][basic-shape]s
//!
//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
use cssparser::{Parser, ToCss};
use parser::{ParserContext, Parse};
use properties::shorthands::{parse_four_sides, serialize_four_sides};
use std::fmt;
use url::Url;
use values::computed::basic_shape as computed_basic_shape;
use values::computed::{Context, ToComputedValue, ComputedValueAsSpecified};
use values::specified::UrlExtraData;
use values::specified::position::Position;
use values::specified::{BorderRadiusSize, LengthOrPercentage, Percentage};
/// A shape source, for some reference box
///
/// clip-path uses ShapeSource<GeometryBox>,
/// shape-outside uses ShapeSource<ShapeBox>
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum ShapeSource<T> {
Url(Url, UrlExtraData),
Shape(BasicShape, Option<T>),
Box(T),
None,
}
impl<T> Default for ShapeSource<T> {
fn default() -> Self {
ShapeSource::None
}
}
impl<T: ToCss> ToCss for ShapeSource<T> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
use values::LocalToCss;
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<T: Parse + PartialEq + Copy> ShapeSource<T> {
pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
if let Ok(_) = input.try(|input| input.expect_ident_matching("none")) {
Ok(ShapeSource::None)
} else if let Ok(url) = input.try(|input| input.expect_url()) {
match UrlExtraData::make_from(context) {
Some(extra_data) => {
Ok(ShapeSource::Url(context.parse_url(&url), extra_data))
},
None => Err(()),
}
} else {
fn parse_component<U: Parse>(input: &mut Parser, component: &mut Option<U>) -> bool {
if component.is_some() {
return false; // already parsed this component
}
*component = input.try(U::parse).ok();
component.is_some()
}
let mut shape = None;
let mut reference = None;
loop {
if !parse_component(input, &mut shape) &&
!parse_component(input, &mut reference) {
break;
}
}
match (shape, reference) {
(Some(shape), _) => Ok(ShapeSource::Shape(shape, reference)),
(None, Some(reference)) => Ok(ShapeSource::Box(reference)),
(None, None) => Err(()),
}
}
}
}
impl<T: ToComputedValue> ToComputedValue for ShapeSource<T> {
type ComputedValue = computed_basic_shape::ShapeSource<T::ComputedValue>;
#[inline]
fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
match *self {
ShapeSource::Url(ref url, ref data) => {
computed_basic_shape::ShapeSource::Url(url.clone(), data.clone())
}
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,
}
}
}
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum BasicShape {
Inset(InsetRect),
Circle(Circle),
Ellipse(Ellipse),
Polygon(Polygon),
}
impl Parse for BasicShape {
fn parse(input: &mut Parser) -> Result<BasicShape, ()> {
match_ignore_ascii_case! { try!(input.expect_function()),
"inset" => {
Ok(BasicShape::Inset(
try!(input.parse_nested_block(InsetRect::parse_function_arguments))))
},
"circle" => {
Ok(BasicShape::Circle(
try!(input.parse_nested_block(Circle::parse_function_arguments))))
},
"ellipse" => {
Ok(BasicShape::Ellipse(
try!(input.parse_nested_block(Ellipse::parse_function_arguments))))
},
"polygon" => {
Ok(BasicShape::Polygon(
try!(input.parse_nested_block(Polygon::parse_function_arguments))))
},
_ => Err(())
}
}
}
impl ToCss for BasicShape {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
BasicShape::Inset(rect) => rect.to_css(dest),
BasicShape::Circle(circle) => circle.to_css(dest),
BasicShape::Ellipse(e) => e.to_css(dest),
BasicShape::Polygon(ref poly) => poly.to_css(dest),
}
}
}
impl ToComputedValue for BasicShape {
type ComputedValue = computed_basic_shape::BasicShape;
#[inline]
fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
match *self {
BasicShape::Inset(rect) => computed_basic_shape::BasicShape::Inset(rect.to_computed_value(cx)),
BasicShape::Circle(circle) => computed_basic_shape::BasicShape::Circle(circle.to_computed_value(cx)),
BasicShape::Ellipse(e) => computed_basic_shape::BasicShape::Ellipse(e.to_computed_value(cx)),
BasicShape::Polygon(ref poly) => computed_basic_shape::BasicShape::Polygon(poly.to_computed_value(cx)),
}
}
}
#[derive(Clone, PartialEq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// https://drafts.csswg.org/css-shapes/#funcdef-inset
pub struct InsetRect {
pub top: LengthOrPercentage,
pub right: LengthOrPercentage,
pub bottom: LengthOrPercentage,
pub left: LengthOrPercentage,
pub round: Option<BorderRadius>,
}
impl InsetRect {
pub fn parse(input: &mut Parser) -> Result<InsetRect, ()> {
match_ignore_ascii_case! { try!(input.expect_function()),
"inset" => {
Ok(try!(input.parse_nested_block(InsetRect::parse_function_arguments)))
},
_ => Err(())
}
}
pub fn parse_function_arguments(input: &mut Parser) -> Result<InsetRect, ()> {
let (t, r, b, l) = try!(parse_four_sides(input, LengthOrPercentage::parse));
let mut rect = InsetRect {
top: t,
right: r,
bottom: b,
left: l,
round: None,
};
if let Ok(_) = input.try(|input| input.expect_ident_matching("round")) {
rect.round = Some(try!(BorderRadius::parse(input)));
}
Ok(rect)
}
}
impl ToCss for InsetRect {
// XXXManishearth again, 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 {
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.map(|r| r.to_computed_value(cx)),
}
}
}
#[derive(Clone, PartialEq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// https://drafts.csswg.org/css-shapes/#funcdef-circle
pub struct Circle {
pub radius: ShapeRadius,
pub position: Position,
}
impl Circle {
pub fn parse(input: &mut Parser) -> Result<Circle, ()> {
match_ignore_ascii_case! { try!(input.expect_function()),
"circle" => {
Ok(try!(input.parse_nested_block(Circle::parse_function_arguments)))
},
_ => Err(())
}
}
pub fn parse_function_arguments(input: &mut Parser) -> Result<Circle, ()> {
let radius = input.try(ShapeRadius::parse).ok().unwrap_or_else(Default::default);
let position = if let Ok(_) = input.try(|input| input.expect_ident_matching("at")) {
try!(Position::parse(input))
} else {
// Defaults to origin
Position {
horizontal: LengthOrPercentage::Percentage(Percentage(0.5)),
vertical: LengthOrPercentage::Percentage(Percentage(0.5)),
}
};
Ok(Circle {
radius: radius,
position: position,
})
}
}
impl ToCss for Circle {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
try!(dest.write_str("circle("));
if ShapeRadius::ClosestSide != self.radius {
try!(self.radius.to_css(dest));
try!(dest.write_str(" "));
}
try!(dest.write_str("at "));
try!(self.position.to_css(dest));
dest.write_str(")")
}
}
impl ToComputedValue for Circle {
type ComputedValue = computed_basic_shape::Circle;
#[inline]
fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
computed_basic_shape::Circle {
radius: self.radius.to_computed_value(cx),
position: self.position.to_computed_value(cx),
}
}
}
#[derive(Clone, PartialEq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// https://drafts.csswg.org/css-shapes/#funcdef-ellipse
pub struct Ellipse {
pub semiaxis_x: ShapeRadius,
pub semiaxis_y: ShapeRadius,
pub position: Position,
}
impl Ellipse {
pub fn parse(input: &mut Parser) -> Result<Ellipse, ()> {
match_ignore_ascii_case! { try!(input.expect_function()),
"ellipse" => {
Ok(try!(input.parse_nested_block(Ellipse::parse_function_arguments)))
},
_ => Err(())
}
}
pub fn parse_function_arguments(input: &mut Parser) -> Result<Ellipse, ()> {
let (a, b) = input.try(|input| -> Result<_, ()> {
Ok((try!(ShapeRadius::parse(input)), try!(ShapeRadius::parse(input))))
}).ok().unwrap_or_default();
let position = if let Ok(_) = input.try(|input| input.expect_ident_matching("at")) {
try!(Position::parse(input))
} else {
// Defaults to origin
Position {
horizontal: LengthOrPercentage::Percentage(Percentage(0.5)),
vertical: LengthOrPercentage::Percentage(Percentage(0.5)),
}
};
Ok(Ellipse {
semiaxis_x: a,
semiaxis_y: b,
position: position,
})
}
}
impl ToCss for Ellipse {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
try!(dest.write_str("ellipse("));
if (self.semiaxis_x, self.semiaxis_y) != Default::default() {
try!(self.semiaxis_x.to_css(dest));
try!(dest.write_str(" "));
try!(self.semiaxis_y.to_css(dest));
try!(dest.write_str(" "));
}
try!(dest.write_str("at "));
try!(self.position.to_css(dest));
dest.write_str(")")
}
}
impl ToComputedValue for Ellipse {
type ComputedValue = computed_basic_shape::Ellipse;
#[inline]
fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
computed_basic_shape::Ellipse {
semiaxis_x: self.semiaxis_x.to_computed_value(cx),
semiaxis_y: self.semiaxis_y.to_computed_value(cx),
position: self.position.to_computed_value(cx),
}
}
}
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
/// https://drafts.csswg.org/css-shapes/#funcdef-polygon
pub struct Polygon {
pub fill: FillRule,
pub coordinates: Vec<(LengthOrPercentage, LengthOrPercentage)>,
}
impl Polygon {
pub fn parse(input: &mut Parser) -> Result<Polygon, ()> {
match_ignore_ascii_case! { try!(input.expect_function()),
"polygon" => {
Ok(try!(input.parse_nested_block(Polygon::parse_function_arguments)))
},
_ => Err(())
}
}
pub fn parse_function_arguments(input: &mut Parser) -> Result<Polygon, ()> {
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(input)),
try!(LengthOrPercentage::parse(input))))
}));
Ok(Polygon {
fill: fill,
coordinates: buf,
})
}
}
impl ToCss for Polygon {
fn to_css<W>(&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(),
}
}
}
/// https://drafts.csswg.org/css-shapes/#typedef-shape-radius
#[derive(Clone, PartialEq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum ShapeRadius {
Length(LengthOrPercentage),
ClosestSide,
FarthestSide,
}
impl Default for ShapeRadius {
fn default() -> Self {
ShapeRadius::ClosestSide
}
}
impl ShapeRadius {
pub fn parse(input: &mut Parser) -> Result<ShapeRadius, ()> {
input.try(LengthOrPercentage::parse).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<W>(&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"),
}
}
}
impl ToComputedValue for ShapeRadius {
type ComputedValue = computed_basic_shape::ShapeRadius;
#[inline]
fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
match *self {
ShapeRadius::Length(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,
}
}
}
/// https://drafts.csswg.org/css-backgrounds-3/#border-radius
#[derive(Clone, PartialEq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct BorderRadius {
pub top_left: BorderRadiusSize,
pub top_right: BorderRadiusSize,
pub bottom_right: BorderRadiusSize,
pub bottom_left: BorderRadiusSize,
}
impl ToCss for BorderRadius {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if self.top_left.0.width == self.top_left.0.height &&
self.top_right.0.width == self.top_right.0.height &&
self.bottom_right.0.width == self.bottom_right.0.height &&
self.bottom_left.0.width == self.bottom_left.0.height {
serialize_four_sides((&self.top_left.0.width,
&self.top_right.0.width,
&self.bottom_right.0.width,
&self.bottom_left.0.width),
dest)
} else {
try!(serialize_four_sides((&self.top_left.0.width,
&self.top_right.0.width,
&self.bottom_right.0.width,
&self.bottom_left.0.width),
dest));
try!(dest.write_str(" / "));
serialize_four_sides((&self.top_left.0.height,
&self.top_right.0.height,
&self.bottom_right.0.height,
&self.bottom_left.0.height),
dest)
}
}
}
impl BorderRadius {
pub fn parse(input: &mut Parser) -> Result<BorderRadius, ()> {
let widths = try!(parse_one_set_of_border_values(input));
let heights = if input.try(|input| input.expect_delim('/')).is_ok() {
try!(parse_one_set_of_border_values(input))
} else {
widths.clone()
};
Ok(BorderRadius {
top_left: BorderRadiusSize::new(widths[0], heights[0]),
top_right: BorderRadiusSize::new(widths[1], heights[1]),
bottom_right: BorderRadiusSize::new(widths[2], heights[2]),
bottom_left: BorderRadiusSize::new(widths[3], heights[3]),
})
}
}
fn parse_one_set_of_border_values(mut input: &mut Parser)
-> Result<[LengthOrPercentage; 4], ()> {
let a = try!(LengthOrPercentage::parse(input));
let b = if let Ok(b) = input.try(LengthOrPercentage::parse) {
b
} else {
return Ok([a, a, a, a])
};
let c = if let Ok(c) = input.try(LengthOrPercentage::parse) {
c
} else {
return Ok([a, b, a, b])
};
if let Ok(d) = input.try(LengthOrPercentage::parse) {
Ok([a, b, c, d])
} else {
Ok([a, b, c, b])
}
}
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),
}
}
}
/// https://drafts.csswg.org/css-shapes/#typedef-fill-rule
#[derive(Clone, PartialEq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum FillRule {
NonZero,
EvenOdd,
// 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`
}
impl ComputedValueAsSpecified for FillRule {}
impl FillRule {
pub fn parse(input: &mut Parser) -> Result<FillRule, ()> {
match_ignore_ascii_case! { try!(input.expect_ident()),
"nonzero" => Ok(FillRule::NonZero),
"evenodd" => Ok(FillRule::EvenOdd),
_ => Err(())
}
}
}
impl Default for FillRule {
fn default() -> Self {
FillRule::NonZero
}
}
impl ToCss for FillRule {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
FillRule::NonZero => dest.write_str("nonzero"),
FillRule::EvenOdd => dest.write_str("evenodd"),
}
}
}
/// https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box
#[derive(Clone, PartialEq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum GeometryBox {
Fill,
Stroke,
View,
ShapeBox(ShapeBox),
}
impl Parse for GeometryBox {
fn parse(input: &mut Parser) -> Result<Self, ()> {
if let Ok(shape_box) = input.try(ShapeBox::parse) {
Ok(GeometryBox::ShapeBox(shape_box))
} else {
match_ignore_ascii_case! { try!(input.expect_ident()),
"fill-box" => Ok(GeometryBox::Fill),
"stroke-box" => Ok(GeometryBox::Stroke),
"view-box" => Ok(GeometryBox::View),
_ => Err(())
}
}
}
}
impl ToCss for GeometryBox {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
GeometryBox::Fill => dest.write_str("fill-box"),
GeometryBox::Stroke => dest.write_str("stroke-box"),
GeometryBox::View => dest.write_str("view-box"),
GeometryBox::ShapeBox(s) => s.to_css(dest),
}
}
}
impl ComputedValueAsSpecified for GeometryBox {}
// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box
#[derive(Clone, PartialEq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum ShapeBox {
Margin,
// https://drafts.csswg.org/css-backgrounds-3/#box
Border,
Padding,
Content,
}
impl Parse for ShapeBox {
fn parse(input: &mut Parser) -> Result<Self, ()> {
match_ignore_ascii_case! { try!(input.expect_ident()),
"margin-box" => Ok(ShapeBox::Margin),
"border-box" => Ok(ShapeBox::Border),
"padding-box" => Ok(ShapeBox::Padding),
"content-box" => Ok(ShapeBox::Content),
_ => Err(())
}
}
}
impl ToCss for ShapeBox {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
ShapeBox::Margin => dest.write_str("margin-box"),
ShapeBox::Border => dest.write_str("border-box"),
ShapeBox::Padding => dest.write_str("padding-box"),
ShapeBox::Content => dest.write_str("content-box"),
}
}
}
impl ComputedValueAsSpecified for ShapeBox {}