/* 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 https://mozilla.org/MPL/2.0/. */ //! CSS handling for the computed value of //! [`basic-shape`][basic-shape]s //! //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape use crate::values::animated::{Animate, Procedure}; use crate::values::computed::angle::Angle; use crate::values::computed::url::ComputedUrl; use crate::values::computed::{Image, LengthPercentage, Position}; use crate::values::generics::basic_shape as generic; use crate::values::generics::basic_shape::ShapePosition; use crate::values::specified::svg_path::{CoordPair, PathCommand}; use crate::values::CSSFloat; /// A computed alias for FillRule. pub use crate::values::generics::basic_shape::FillRule; /// A computed `clip-path` value. pub type ClipPath = generic::GenericClipPath; /// A computed `shape-outside` value. pub type ShapeOutside = generic::GenericShapeOutside; /// A computed basic shape. pub type BasicShape = generic::GenericBasicShape; /// The computed value of `inset()`. pub type InsetRect = generic::GenericInsetRect; /// A computed circle. pub type Circle = generic::Circle; /// A computed ellipse. pub type Ellipse = generic::Ellipse; /// The computed value of `ShapeRadius`. pub type ShapeRadius = generic::GenericShapeRadius; /// The computed value of `shape()`. pub type Shape = generic::Shape; /// The computed value of `ShapeCommand`. pub type ShapeCommand = generic::GenericShapeCommand; /// The computed value of `PathOrShapeFunction`. pub type PathOrShapeFunction = generic::GenericPathOrShapeFunction; /// The computed value of `CoordinatePair`. pub type CoordinatePair = generic::CoordinatePair; /// The computed value of 'ControlPoint'. pub type ControlPoint = generic::ControlPoint; /// The computed value of 'RelativeControlPoint'. pub type RelativeControlPoint = generic::RelativeControlPoint; /// The computed value of 'CommandEndPoint'. pub type CommandEndPoint = generic::CommandEndPoint; /// The computed value of hline and vline's endpoint. pub type AxisEndPoint = generic::AxisEndPoint; /// Animate from `Shape` to `Path`, and vice versa. macro_rules! animate_shape { ( $from:ident, $to:ident, $procedure:ident, $from_as_shape:tt, $to_as_shape:tt ) => {{ // Check fill-rule. if $from.fill != $to.fill { return Err(()); } // Check the list of commands. (This is a specialized lists::by_computed_value::animate().) let from_cmds = $from.commands(); let to_cmds = $to.commands(); if from_cmds.len() != to_cmds.len() { return Err(()); } let commands = from_cmds .iter() .zip(to_cmds.iter()) .map(|(from_cmd, to_cmd)| { $from_as_shape(from_cmd).animate(&$to_as_shape(to_cmd), $procedure) }) .collect::, ()>>()?; Ok(Shape { fill: $from.fill, commands: commands.into(), }) }}; } impl Animate for PathOrShapeFunction { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { // Per spec, commands are "the same" if they use the same command keyword, and use the same // keyword. For curve and smooth, they also must have the same number of control // points. Therefore, we don't have to do normalization here. (Note that we do // normalization if we animate from path() to path(). See svg_path.rs for more details.) // // https://drafts.csswg.org/css-shapes-2/#interpolating-shape match (self, other) { (Self::Path(ref from), Self::Path(ref to)) => { from.animate(to, procedure).map(Self::Path) }, (Self::Shape(ref from), Self::Shape(ref to)) => { from.animate(to, procedure).map(Self::Shape) }, (Self::Shape(ref from), Self::Path(ref to)) => { // Animate from shape() to path(). We convert each PathCommand into ShapeCommand, // and return shape(). animate_shape!( from, to, procedure, (|shape_cmd| shape_cmd), (|path_cmd| ShapeCommand::from(path_cmd)) ) .map(Self::Shape) }, (Self::Path(ref from), Self::Shape(ref to)) => { // Animate from path() to shape(). We convert each PathCommand into ShapeCommand, // and return shape(). animate_shape!( from, to, procedure, (|path_cmd| ShapeCommand::from(path_cmd)), (|shape_cmd| shape_cmd) ) .map(Self::Shape) }, } } } impl From<&PathCommand> for ShapeCommand { #[inline] fn from(path: &PathCommand) -> Self { match path { &PathCommand::Close => Self::Close, &PathCommand::Move { ref point } => Self::Move { point: point.into(), }, &PathCommand::Line { ref point } => Self::Move { point: point.into(), }, &PathCommand::HLine { ref x } => Self::HLine { x: x.into() }, &PathCommand::VLine { ref y } => Self::VLine { y: y.into() }, &PathCommand::CubicCurve { ref point, ref control1, ref control2, } => Self::CubicCurve { point: point.into(), control1: control1.into(), control2: control2.into(), }, &PathCommand::QuadCurve { ref point, ref control1, } => Self::QuadCurve { point: point.into(), control1: control1.into(), }, &PathCommand::SmoothCubic { ref point, ref control2, } => Self::SmoothCubic { point: point.into(), control2: control2.into(), }, &PathCommand::SmoothQuad { ref point } => Self::SmoothQuad { point: point.into(), }, &PathCommand::Arc { ref point, ref radii, arc_sweep, arc_size, rotate, } => Self::Arc { point: point.into(), radii: radii.into(), arc_sweep, arc_size, rotate: Angle::from_degrees(rotate), }, } } } impl From<&CoordPair> for CoordinatePair { #[inline] fn from(p: &CoordPair) -> Self { use crate::values::computed::CSSPixelLength; Self::new( LengthPercentage::new_length(CSSPixelLength::new(p.x)), LengthPercentage::new_length(CSSPixelLength::new(p.y)), ) } } impl From<&ShapePosition> for Position { #[inline] fn from(p: &ShapePosition) -> Self { use crate::values::computed::CSSPixelLength; Self::new( LengthPercentage::new_length(CSSPixelLength::new(p.horizontal)), LengthPercentage::new_length(CSSPixelLength::new(p.vertical)), ) } } impl From<&generic::CommandEndPoint, CSSFloat>> for CommandEndPoint { #[inline] fn from(p: &generic::CommandEndPoint, CSSFloat>) -> Self { match p { generic::CommandEndPoint::ToPosition(pos) => Self::ToPosition(pos.into()), generic::CommandEndPoint::ByCoordinate(coord) => Self::ByCoordinate(coord.into()), } } } impl From<&generic::AxisEndPoint> for AxisEndPoint { #[inline] fn from(p: &generic::AxisEndPoint) -> Self { use crate::values::computed::CSSPixelLength; use generic::AxisPosition; match p { generic::AxisEndPoint::ToPosition(AxisPosition::LengthPercent(lp)) => Self::ToPosition( AxisPosition::LengthPercent(LengthPercentage::new_length(CSSPixelLength::new(*lp))), ), generic::AxisEndPoint::ToPosition(AxisPosition::Keyword(_)) => { unreachable!("Invalid state: SVG path commands cannot contain a keyword.") }, generic::AxisEndPoint::ByCoordinate(pos) => { Self::ByCoordinate(LengthPercentage::new_length(CSSPixelLength::new(*pos))) }, } } } impl From<&generic::ControlPoint, CSSFloat>> for ControlPoint { #[inline] fn from(p: &generic::ControlPoint, CSSFloat>) -> Self { match p { generic::ControlPoint::Absolute(pos) => Self::Absolute(pos.into()), generic::ControlPoint::Relative(point) => Self::Relative(RelativeControlPoint { coord: CoordinatePair::from(&point.coord), reference: point.reference, }), } } } impl From<&generic::ArcRadii> for generic::ArcRadii { #[inline] fn from(p: &generic::ArcRadii) -> Self { use crate::values::computed::CSSPixelLength; Self { rx: LengthPercentage::new_length(CSSPixelLength::new(p.rx)), ry: p .ry .map(|v| LengthPercentage::new_length(CSSPixelLength::new(v))), } } }