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:tt,
74        $to_as_shape:tt
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(ref from), Self::Path(ref to)) => {
113                from.animate(to, procedure).map(Self::Path)
114            },
115            (Self::Shape(ref from), Self::Shape(ref to)) => {
116                from.animate(to, procedure).map(Self::Shape)
117            },
118            (Self::Shape(ref from), Self::Path(ref to)) => {
119                // Animate from shape() to path(). We convert each PathCommand into ShapeCommand,
120                // and return shape().
121                animate_shape!(
122                    from,
123                    to,
124                    procedure,
125                    (|shape_cmd| shape_cmd),
126                    (|path_cmd| ShapeCommand::from(path_cmd))
127                )
128                .map(Self::Shape)
129            },
130            (Self::Path(ref from), Self::Shape(ref to)) => {
131                // Animate from path() to shape(). We convert each PathCommand into ShapeCommand,
132                // and return shape().
133                animate_shape!(
134                    from,
135                    to,
136                    procedure,
137                    (|path_cmd| ShapeCommand::from(path_cmd)),
138                    (|shape_cmd| shape_cmd)
139                )
140                .map(Self::Shape)
141            },
142        }
143    }
144}
145
146impl From<&PathCommand> for ShapeCommand {
147    #[inline]
148    fn from(path: &PathCommand) -> Self {
149        match path {
150            &PathCommand::Close => Self::Close,
151            &PathCommand::Move { ref point } => Self::Move {
152                point: point.into(),
153            },
154            &PathCommand::Line { ref point } => Self::Move {
155                point: point.into(),
156            },
157            &PathCommand::HLine { ref x } => Self::HLine { x: x.into() },
158            &PathCommand::VLine { ref y } => Self::VLine { y: y.into() },
159            &PathCommand::CubicCurve {
160                ref point,
161                ref control1,
162                ref control2,
163            } => Self::CubicCurve {
164                point: point.into(),
165                control1: control1.into(),
166                control2: control2.into(),
167            },
168            &PathCommand::QuadCurve {
169                ref point,
170                ref control1,
171            } => Self::QuadCurve {
172                point: point.into(),
173                control1: control1.into(),
174            },
175            &PathCommand::SmoothCubic {
176                ref point,
177                ref control2,
178            } => Self::SmoothCubic {
179                point: point.into(),
180                control2: control2.into(),
181            },
182            &PathCommand::SmoothQuad { ref point } => Self::SmoothQuad {
183                point: point.into(),
184            },
185            &PathCommand::Arc {
186                ref point,
187                ref radii,
188                arc_sweep,
189                arc_size,
190                rotate,
191            } => Self::Arc {
192                point: point.into(),
193                radii: radii.into(),
194                arc_sweep,
195                arc_size,
196                rotate: Angle::from_degrees(rotate),
197            },
198        }
199    }
200}
201
202impl From<&CoordPair> for CoordinatePair {
203    #[inline]
204    fn from(p: &CoordPair) -> Self {
205        use crate::values::computed::CSSPixelLength;
206        Self::new(
207            LengthPercentage::new_length(CSSPixelLength::new(p.x)),
208            LengthPercentage::new_length(CSSPixelLength::new(p.y)),
209        )
210    }
211}
212
213impl From<&SVGPathPosition> for Position {
214    #[inline]
215    fn from(p: &SVGPathPosition) -> Self {
216        use crate::values::computed::CSSPixelLength;
217        Self::new(
218            LengthPercentage::new_length(CSSPixelLength::new(p.horizontal)),
219            LengthPercentage::new_length(CSSPixelLength::new(p.vertical)),
220        )
221    }
222}
223
224impl From<&generic::CommandEndPoint<SVGPathPosition, CSSFloat>> for CommandEndPoint {
225    #[inline]
226    fn from(p: &generic::CommandEndPoint<SVGPathPosition, CSSFloat>) -> Self {
227        match p {
228            generic::CommandEndPoint::ToPosition(pos) => Self::ToPosition(pos.into()),
229            generic::CommandEndPoint::ByCoordinate(coord) => Self::ByCoordinate(coord.into()),
230        }
231    }
232}
233
234impl From<&generic::AxisEndPoint<CSSFloat>> for AxisEndPoint {
235    #[inline]
236    fn from(p: &generic::AxisEndPoint<CSSFloat>) -> Self {
237        use crate::values::computed::CSSPixelLength;
238        use generic::AxisPosition;
239        match p {
240            generic::AxisEndPoint::ToPosition(AxisPosition::LengthPercent(lp)) => Self::ToPosition(
241                AxisPosition::LengthPercent(LengthPercentage::new_length(CSSPixelLength::new(*lp))),
242            ),
243            generic::AxisEndPoint::ToPosition(AxisPosition::Keyword(_)) => {
244                unreachable!("Invalid state: SVG path commands cannot contain a keyword.")
245            },
246            generic::AxisEndPoint::ByCoordinate(pos) => {
247                Self::ByCoordinate(LengthPercentage::new_length(CSSPixelLength::new(*pos)))
248            },
249        }
250    }
251}
252
253impl From<&generic::ControlPoint<SVGPathPosition, CSSFloat>> for ControlPoint {
254    #[inline]
255    fn from(p: &generic::ControlPoint<SVGPathPosition, CSSFloat>) -> Self {
256        match p {
257            generic::ControlPoint::Absolute(pos) => Self::Absolute(pos.into()),
258            generic::ControlPoint::Relative(point) => Self::Relative(RelativeControlPoint {
259                coord: CoordinatePair::from(&point.coord),
260                reference: point.reference,
261            }),
262        }
263    }
264}
265
266impl From<&generic::ArcRadii<CSSFloat>> for generic::ArcRadii<LengthPercentage> {
267    #[inline]
268    fn from(p: &generic::ArcRadii<CSSFloat>) -> Self {
269        use crate::values::computed::CSSPixelLength;
270        Self {
271            rx: LengthPercentage::new_length(CSSPixelLength::new(p.rx)),
272            ry: p
273                .ry
274                .map(|v| LengthPercentage::new_length(CSSPixelLength::new(v))),
275        }
276    }
277}