Skip to main content

style/values/computed/
basic_shape.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! CSS handling for the computed value of
6//! [`basic-shape`][basic-shape]s
7//!
8//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
9
10use crate::values::animated::{Animate, Procedure};
11use crate::values::computed::angle::Angle;
12use crate::values::computed::url::ComputedUrl;
13use crate::values::computed::{Image, LengthPercentage, Position};
14use crate::values::generics::basic_shape as generic;
15use crate::values::specified::svg_path::{CoordPair, PathCommand, SVGPathPosition};
16use crate::values::CSSFloat;
17
18/// A computed alias for FillRule.
19pub use crate::values::generics::basic_shape::FillRule;
20
21/// A computed `clip-path` value.
22pub type ClipPath = generic::GenericClipPath<BasicShape, ComputedUrl>;
23
24/// A computed `shape-outside` value.
25pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>;
26
27/// A computed basic shape.
28pub type BasicShape = generic::GenericBasicShape<Angle, Position, LengthPercentage, InsetRect>;
29
30/// The computed value of `inset()`.
31pub type InsetRect = generic::GenericInsetRect<LengthPercentage>;
32
33/// A computed circle.
34pub type Circle = generic::Circle<Position, LengthPercentage>;
35
36/// A computed ellipse.
37pub type Ellipse = generic::Ellipse<Position, LengthPercentage>;
38
39/// The computed value of `ShapeRadius`.
40pub type ShapeRadius = generic::GenericShapeRadius<LengthPercentage>;
41
42/// The computed value of `shape()`.
43pub type Shape = generic::Shape<Angle, Position, LengthPercentage>;
44
45/// The computed value of `ShapeCommand`.
46pub type ShapeCommand = generic::GenericShapeCommand<Angle, Position, LengthPercentage>;
47
48/// The computed value of `PathOrShapeFunction`.
49pub type PathOrShapeFunction =
50    generic::GenericPathOrShapeFunction<Angle, Position, LengthPercentage>;
51
52/// The computed value of `CoordinatePair`.
53pub type CoordinatePair = generic::CoordinatePair<LengthPercentage>;
54
55/// The computed value of 'ControlPoint'.
56pub type ControlPoint = generic::ControlPoint<Position, LengthPercentage>;
57
58/// The computed value of 'RelativeControlPoint'.
59pub type RelativeControlPoint = generic::RelativeControlPoint<LengthPercentage>;
60
61/// The computed value of 'CommandEndPoint'.
62pub type CommandEndPoint = generic::CommandEndPoint<Position, LengthPercentage>;
63
64/// The computed value of hline and vline's endpoint.
65pub type AxisEndPoint = generic::AxisEndPoint<LengthPercentage>;
66
67/// Animate from `Shape` to `Path`, and vice versa.
68macro_rules! animate_shape {
69    (
70        $from:ident,
71        $to:ident,
72        $procedure:ident,
73        $from_as_shape:expr,
74        $to_as_shape:expr
75    ) => {{
76        // Check fill-rule.
77        if $from.fill != $to.fill {
78            return Err(());
79        }
80
81        // Check the list of commands. (This is a specialized lists::by_computed_value::animate().)
82        let from_cmds = $from.commands();
83        let to_cmds = $to.commands();
84        if from_cmds.len() != to_cmds.len() {
85            return Err(());
86        }
87        let commands = from_cmds
88            .iter()
89            .zip(to_cmds.iter())
90            .map(|(from_cmd, to_cmd)| {
91                $from_as_shape(from_cmd).animate(&$to_as_shape(to_cmd), $procedure)
92            })
93            .collect::<Result<Vec<ShapeCommand>, ()>>()?;
94
95        Ok(Shape {
96            fill: $from.fill,
97            commands: commands.into(),
98        })
99    }};
100}
101
102impl Animate for PathOrShapeFunction {
103    #[inline]
104    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
105        // Per spec, commands are "the same" if they use the same command keyword, and use the same
106        // <by-to> keyword. For curve and smooth, they also must have the same number of control
107        // points. Therefore, we don't have to do normalization here. (Note that we do
108        // normalization if we animate from path() to path(). See svg_path.rs for more details.)
109        //
110        // https://drafts.csswg.org/css-shapes-2/#interpolating-shape
111        match (self, other) {
112            (Self::Path(from), Self::Path(to)) => from.animate(to, procedure).map(Self::Path),
113            (Self::Shape(from), Self::Shape(to)) => from.animate(to, procedure).map(Self::Shape),
114            (Self::Shape(from), Self::Path(to)) => {
115                // Animate from shape() to path(). We convert each PathCommand into ShapeCommand,
116                // and return shape().
117                animate_shape!(
118                    from,
119                    to,
120                    procedure,
121                    (|shape_cmd| shape_cmd),
122                    ShapeCommand::from
123                )
124                .map(Self::Shape)
125            },
126            (Self::Path(from), Self::Shape(to)) => {
127                // Animate from path() to shape(). We convert each PathCommand into ShapeCommand,
128                // and return shape().
129                animate_shape!(
130                    from,
131                    to,
132                    procedure,
133                    ShapeCommand::from,
134                    (|shape_cmd| shape_cmd)
135                )
136                .map(Self::Shape)
137            },
138        }
139    }
140}
141
142impl From<&PathCommand> for ShapeCommand {
143    #[inline]
144    fn from(path: &PathCommand) -> Self {
145        match path {
146            &PathCommand::Close => Self::Close,
147            PathCommand::Move { point } => Self::Move {
148                point: point.into(),
149            },
150            PathCommand::Line { point } => Self::Move {
151                point: point.into(),
152            },
153            PathCommand::HLine { x } => Self::HLine { x: x.into() },
154            PathCommand::VLine { y } => Self::VLine { y: y.into() },
155            PathCommand::CubicCurve {
156                point,
157                control1,
158                control2,
159            } => Self::CubicCurve {
160                point: point.into(),
161                control1: control1.into(),
162                control2: control2.into(),
163            },
164            PathCommand::QuadCurve { point, control1 } => Self::QuadCurve {
165                point: point.into(),
166                control1: control1.into(),
167            },
168            PathCommand::SmoothCubic { point, control2 } => Self::SmoothCubic {
169                point: point.into(),
170                control2: control2.into(),
171            },
172            PathCommand::SmoothQuad { point } => Self::SmoothQuad {
173                point: point.into(),
174            },
175            &PathCommand::Arc {
176                ref point,
177                ref radii,
178                arc_sweep,
179                arc_size,
180                rotate,
181            } => Self::Arc {
182                point: point.into(),
183                radii: radii.into(),
184                arc_sweep,
185                arc_size,
186                rotate: Angle::from_degrees(rotate),
187            },
188        }
189    }
190}
191
192impl From<&CoordPair> for CoordinatePair {
193    #[inline]
194    fn from(p: &CoordPair) -> Self {
195        use crate::values::computed::CSSPixelLength;
196        Self::new(
197            LengthPercentage::new_length(CSSPixelLength::new(p.x)),
198            LengthPercentage::new_length(CSSPixelLength::new(p.y)),
199        )
200    }
201}
202
203impl From<&SVGPathPosition> for Position {
204    #[inline]
205    fn from(p: &SVGPathPosition) -> Self {
206        use crate::values::computed::CSSPixelLength;
207        Self::new(
208            LengthPercentage::new_length(CSSPixelLength::new(p.horizontal)),
209            LengthPercentage::new_length(CSSPixelLength::new(p.vertical)),
210        )
211    }
212}
213
214impl From<&generic::CommandEndPoint<SVGPathPosition, CSSFloat>> for CommandEndPoint {
215    #[inline]
216    fn from(p: &generic::CommandEndPoint<SVGPathPosition, CSSFloat>) -> Self {
217        match p {
218            generic::CommandEndPoint::ToPosition(pos) => Self::ToPosition(pos.into()),
219            generic::CommandEndPoint::ByCoordinate(coord) => Self::ByCoordinate(coord.into()),
220        }
221    }
222}
223
224impl From<&generic::AxisEndPoint<CSSFloat>> for AxisEndPoint {
225    #[inline]
226    fn from(p: &generic::AxisEndPoint<CSSFloat>) -> Self {
227        use crate::values::computed::CSSPixelLength;
228        use generic::AxisPosition;
229        match p {
230            generic::AxisEndPoint::ToPosition(AxisPosition::LengthPercent(lp)) => Self::ToPosition(
231                AxisPosition::LengthPercent(LengthPercentage::new_length(CSSPixelLength::new(*lp))),
232            ),
233            generic::AxisEndPoint::ToPosition(AxisPosition::Keyword(_)) => {
234                unreachable!("Invalid state: SVG path commands cannot contain a keyword.")
235            },
236            generic::AxisEndPoint::ByCoordinate(pos) => {
237                Self::ByCoordinate(LengthPercentage::new_length(CSSPixelLength::new(*pos)))
238            },
239        }
240    }
241}
242
243impl From<&generic::ControlPoint<SVGPathPosition, CSSFloat>> for ControlPoint {
244    #[inline]
245    fn from(p: &generic::ControlPoint<SVGPathPosition, CSSFloat>) -> Self {
246        match p {
247            generic::ControlPoint::Absolute(pos) => Self::Absolute(pos.into()),
248            generic::ControlPoint::Relative(point) => Self::Relative(RelativeControlPoint {
249                coord: CoordinatePair::from(&point.coord),
250                reference: point.reference,
251            }),
252        }
253    }
254}
255
256impl From<&generic::ArcRadii<CSSFloat>> for generic::ArcRadii<LengthPercentage> {
257    #[inline]
258    fn from(p: &generic::ArcRadii<CSSFloat>) -> Self {
259        use crate::values::computed::CSSPixelLength;
260        Self {
261            rx: LengthPercentage::new_length(CSSPixelLength::new(p.rx)),
262            ry: p
263                .ry
264                .map(|v| LengthPercentage::new_length(CSSPixelLength::new(v))),
265        }
266    }
267}