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