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 //! Specified types for CSS values that are related to motion path.
6 
7 use crate::parser::{Parse, ParserContext};
8 use crate::values::computed::motion::OffsetRotate as ComputedOffsetRotate;
9 use crate::values::computed::{Context, ToComputedValue};
10 use crate::values::generics::motion::{GenericOffsetPath, RayFunction, RaySize};
11 use crate::values::specified::{Angle, SVGPathData};
12 use crate::Zero;
13 use cssparser::Parser;
14 use style_traits::{ParseError, StyleParseErrorKind};
15 
16 /// The specified value of `offset-path`.
17 pub type OffsetPath = GenericOffsetPath<Angle>;
18 
19 #[cfg(feature = "gecko")]
is_ray_enabled() -> bool20 fn is_ray_enabled() -> bool {
21     static_prefs::pref!("layout.css.motion-path-ray.enabled")
22 }
23 #[cfg(feature = "servo")]
is_ray_enabled() -> bool24 fn is_ray_enabled() -> bool {
25     false
26 }
27 
28 impl Parse for RayFunction<Angle> {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>29     fn parse<'i, 't>(
30         context: &ParserContext,
31         input: &mut Parser<'i, 't>,
32     ) -> Result<Self, ParseError<'i>> {
33         if !is_ray_enabled() {
34             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
35         }
36 
37         let mut angle = None;
38         let mut size = None;
39         let mut contain = false;
40         loop {
41             if angle.is_none() {
42                 angle = input.try_parse(|i| Angle::parse(context, i)).ok();
43             }
44 
45             if size.is_none() {
46                 size = input.try_parse(RaySize::parse).ok();
47                 if size.is_some() {
48                     continue;
49                 }
50             }
51 
52             if !contain {
53                 contain = input
54                     .try_parse(|i| i.expect_ident_matching("contain"))
55                     .is_ok();
56                 if contain {
57                     continue;
58                 }
59             }
60             break;
61         }
62 
63         if angle.is_none() || size.is_none() {
64             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
65         }
66 
67         Ok(RayFunction {
68             angle: angle.unwrap(),
69             size: size.unwrap(),
70             contain,
71         })
72     }
73 }
74 
75 impl Parse for OffsetPath {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>76     fn parse<'i, 't>(
77         context: &ParserContext,
78         input: &mut Parser<'i, 't>,
79     ) -> Result<Self, ParseError<'i>> {
80         // Parse none.
81         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
82             return Ok(OffsetPath::none());
83         }
84 
85         // Parse possible functions.
86         let location = input.current_source_location();
87         let function = input.expect_function()?.clone();
88         input.parse_nested_block(move |i| {
89             match_ignore_ascii_case! { &function,
90                 // Bug 1186329: Implement the parser for <basic-shape>, <geometry-box>,
91                 // and <url>.
92                 "path" => SVGPathData::parse(context, i).map(GenericOffsetPath::Path),
93                 "ray" => RayFunction::parse(context, i).map(GenericOffsetPath::Ray),
94                 _ => {
95                     Err(location.new_custom_error(
96                         StyleParseErrorKind::UnexpectedFunction(function.clone())
97                     ))
98                 },
99             }
100         })
101     }
102 }
103 
104 /// The direction of offset-rotate.
105 #[derive(
106     Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
107 )]
108 #[repr(u8)]
109 pub enum OffsetRotateDirection {
110     /// Unspecified direction keyword.
111     #[css(skip)]
112     None,
113     /// 0deg offset (face forward).
114     Auto,
115     /// 180deg offset (face backward).
116     Reverse,
117 }
118 
119 impl OffsetRotateDirection {
120     /// Returns true if it is none (i.e. the keyword is not specified).
121     #[inline]
is_none(&self) -> bool122     fn is_none(&self) -> bool {
123         *self == OffsetRotateDirection::None
124     }
125 }
126 
127 #[inline]
direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool128 fn direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool {
129     !direction.is_none() && angle.is_zero()
130 }
131 
132 /// The specified offset-rotate.
133 /// The syntax is: "[ auto | reverse ] || <angle>"
134 ///
135 /// https://drafts.fxtf.org/motion-1/#offset-rotate-property
136 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
137 pub struct OffsetRotate {
138     /// [auto | reverse].
139     #[css(skip_if = "OffsetRotateDirection::is_none")]
140     direction: OffsetRotateDirection,
141     /// <angle>.
142     /// If direction is None, this is a fixed angle which indicates a
143     /// constant clockwise rotation transformation applied to it by this
144     /// specified rotation angle. Otherwise, the angle will be added to
145     /// the angle of the direction in layout.
146     #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")]
147     angle: Angle,
148 }
149 
150 impl OffsetRotate {
151     /// Returns the initial value, auto.
152     #[inline]
auto() -> Self153     pub fn auto() -> Self {
154         OffsetRotate {
155             direction: OffsetRotateDirection::Auto,
156             angle: Angle::zero(),
157         }
158     }
159 
160     /// Returns true if self is auto 0deg.
161     #[inline]
is_auto(&self) -> bool162     pub fn is_auto(&self) -> bool {
163         self.direction == OffsetRotateDirection::Auto && self.angle.is_zero()
164     }
165 }
166 
167 impl Parse for OffsetRotate {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>168     fn parse<'i, 't>(
169         context: &ParserContext,
170         input: &mut Parser<'i, 't>,
171     ) -> Result<Self, ParseError<'i>> {
172         let location = input.current_source_location();
173         let mut direction = input.try_parse(OffsetRotateDirection::parse);
174         let angle = input.try_parse(|i| Angle::parse(context, i));
175         if direction.is_err() {
176             // The direction and angle could be any order, so give it a change to parse
177             // direction again.
178             direction = input.try_parse(OffsetRotateDirection::parse);
179         }
180 
181         if direction.is_err() && angle.is_err() {
182             return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
183         }
184 
185         Ok(OffsetRotate {
186             direction: direction.unwrap_or(OffsetRotateDirection::None),
187             angle: angle.unwrap_or(Zero::zero()),
188         })
189     }
190 }
191 
192 impl ToComputedValue for OffsetRotate {
193     type ComputedValue = ComputedOffsetRotate;
194 
195     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue196     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
197         use crate::values::computed::Angle as ComputedAngle;
198 
199         ComputedOffsetRotate {
200             auto: !self.direction.is_none(),
201             angle: if self.direction == OffsetRotateDirection::Reverse {
202                 // The computed value should always convert "reverse" into "auto".
203                 // e.g. "reverse calc(20deg + 10deg)" => "auto 210deg"
204                 self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0)
205             } else {
206                 self.angle.to_computed_value(context)
207             },
208         }
209     }
210 
211     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self212     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
213         OffsetRotate {
214             direction: if computed.auto {
215                 OffsetRotateDirection::Auto
216             } else {
217                 OffsetRotateDirection::None
218             },
219             angle: ToComputedValue::from_computed_value(&computed.angle),
220         }
221     }
222 }
223