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