1use crate::parser::{Parse, ParserContext};
8use crate::values::computed::motion::OffsetRotate as ComputedOffsetRotate;
9use crate::values::computed::{Context, ToComputedValue};
10use crate::values::generics::motion as generics;
11use crate::values::specified::basic_shape::BasicShape;
12use crate::values::specified::position::{HorizontalPosition, VerticalPosition};
13use crate::values::specified::url::SpecifiedUrl;
14use crate::values::specified::{Angle, Position};
15use crate::Zero;
16use cssparser::Parser;
17use style_traits::{ParseError, StyleParseErrorKind};
18
19pub type RayFunction = generics::GenericRayFunction<Angle, Position>;
21
22pub type OffsetPathFunction =
24 generics::GenericOffsetPathFunction<BasicShape, RayFunction, SpecifiedUrl>;
25
26pub type OffsetPath = generics::GenericOffsetPath<OffsetPathFunction>;
28
29pub type OffsetPosition = generics::GenericOffsetPosition<HorizontalPosition, VerticalPosition>;
31
32#[allow(missing_docs)]
38#[derive(
39 Animate,
40 Clone,
41 ComputeSquaredDistance,
42 Copy,
43 Debug,
44 Deserialize,
45 MallocSizeOf,
46 Parse,
47 PartialEq,
48 Serialize,
49 SpecifiedValueInfo,
50 ToAnimatedValue,
51 ToComputedValue,
52 ToCss,
53 ToResolvedValue,
54 ToShmem,
55)]
56#[repr(u8)]
57pub enum CoordBox {
58 ContentBox,
59 PaddingBox,
60 BorderBox,
61 FillBox,
62 StrokeBox,
63 ViewBox,
64}
65
66impl CoordBox {
67 #[inline]
69 pub fn is_default(&self) -> bool {
70 matches!(*self, Self::BorderBox)
71 }
72}
73
74impl Parse for RayFunction {
75 fn parse<'i, 't>(
76 context: &ParserContext,
77 input: &mut Parser<'i, 't>,
78 ) -> Result<Self, ParseError<'i>> {
79 input.expect_function_matching("ray")?;
80 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
81 }
82}
83
84impl RayFunction {
85 fn parse_function_arguments<'i, 't>(
87 context: &ParserContext,
88 input: &mut Parser<'i, 't>,
89 ) -> Result<Self, ParseError<'i>> {
90 use crate::values::specified::PositionOrAuto;
91
92 let mut angle = None;
93 let mut size = None;
94 let mut contain = false;
95 let mut position = None;
96 loop {
97 if angle.is_none() {
98 angle = input.try_parse(|i| Angle::parse(context, i)).ok();
99 }
100
101 if size.is_none() {
102 size = input.try_parse(generics::RaySize::parse).ok();
103 if size.is_some() {
104 continue;
105 }
106 }
107
108 if !contain {
109 contain = input
110 .try_parse(|i| i.expect_ident_matching("contain"))
111 .is_ok();
112 if contain {
113 continue;
114 }
115 }
116
117 if position.is_none() {
118 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
119 let pos = Position::parse(context, input)?;
120 position = Some(PositionOrAuto::Position(pos));
121 }
122
123 if position.is_some() {
124 continue;
125 }
126 }
127 break;
128 }
129
130 if angle.is_none() {
131 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
132 }
133
134 Ok(RayFunction {
135 angle: angle.unwrap(),
136 size: size.unwrap_or(generics::RaySize::ClosestSide),
138 contain,
139 position: position.unwrap_or(PositionOrAuto::auto()),
140 })
141 }
142}
143
144impl Parse for OffsetPathFunction {
145 fn parse<'i, 't>(
146 context: &ParserContext,
147 input: &mut Parser<'i, 't>,
148 ) -> Result<Self, ParseError<'i>> {
149 use crate::values::specified::basic_shape::{AllowedBasicShapes, ShapeType};
150
151 if let Ok(ray) = input.try_parse(|i| RayFunction::parse(context, i)) {
154 return Ok(OffsetPathFunction::Ray(ray));
155 }
156
157 if static_prefs::pref!("layout.css.motion-path-url.enabled") {
158 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
159 return Ok(OffsetPathFunction::Url(url));
160 }
161 }
162
163 BasicShape::parse(context, input, AllowedBasicShapes::ALL, ShapeType::Outline)
164 .map(OffsetPathFunction::Shape)
165 }
166}
167
168impl Parse for OffsetPath {
169 fn parse<'i, 't>(
170 context: &ParserContext,
171 input: &mut Parser<'i, 't>,
172 ) -> Result<Self, ParseError<'i>> {
173 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
175 return Ok(OffsetPath::none());
176 }
177
178 let mut path = None;
179 let mut coord_box = None;
180 loop {
181 if path.is_none() {
182 path = input
183 .try_parse(|i| OffsetPathFunction::parse(context, i))
184 .ok();
185 }
186
187 if coord_box.is_none() {
188 coord_box = input.try_parse(CoordBox::parse).ok();
189 if coord_box.is_some() {
190 continue;
191 }
192 }
193 break;
194 }
195
196 if let Some(p) = path {
197 return Ok(OffsetPath::OffsetPath {
198 path: Box::new(p),
199 coord_box: coord_box.unwrap_or(CoordBox::BorderBox),
200 });
201 }
202
203 match coord_box {
204 Some(c) => Ok(OffsetPath::CoordBox(c)),
205 None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
206 }
207 }
208}
209
210#[derive(
212 Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
213)]
214#[repr(u8)]
215pub enum OffsetRotateDirection {
216 #[css(skip)]
218 None,
219 Auto,
221 Reverse,
223}
224
225impl OffsetRotateDirection {
226 #[inline]
228 fn is_none(&self) -> bool {
229 *self == OffsetRotateDirection::None
230 }
231}
232
233#[inline]
234fn direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool {
235 !direction.is_none() && angle.is_zero()
236}
237
238#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
243pub struct OffsetRotate {
244 #[css(skip_if = "OffsetRotateDirection::is_none")]
246 direction: OffsetRotateDirection,
247 #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")]
253 angle: Angle,
254}
255
256impl OffsetRotate {
257 #[inline]
259 pub fn auto() -> Self {
260 OffsetRotate {
261 direction: OffsetRotateDirection::Auto,
262 angle: Angle::zero(),
263 }
264 }
265
266 #[inline]
268 pub fn is_auto(&self) -> bool {
269 self.direction == OffsetRotateDirection::Auto && self.angle.is_zero()
270 }
271}
272
273impl Parse for OffsetRotate {
274 fn parse<'i, 't>(
275 context: &ParserContext,
276 input: &mut Parser<'i, 't>,
277 ) -> Result<Self, ParseError<'i>> {
278 let location = input.current_source_location();
279 let mut direction = input.try_parse(OffsetRotateDirection::parse);
280 let angle = input.try_parse(|i| Angle::parse(context, i));
281 if direction.is_err() {
282 direction = input.try_parse(OffsetRotateDirection::parse);
285 }
286
287 if direction.is_err() && angle.is_err() {
288 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
289 }
290
291 Ok(OffsetRotate {
292 direction: direction.unwrap_or(OffsetRotateDirection::None),
293 angle: angle.unwrap_or(Zero::zero()),
294 })
295 }
296}
297
298impl ToComputedValue for OffsetRotate {
299 type ComputedValue = ComputedOffsetRotate;
300
301 #[inline]
302 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
303 use crate::values::computed::Angle as ComputedAngle;
304
305 ComputedOffsetRotate {
306 auto: !self.direction.is_none(),
307 angle: if self.direction == OffsetRotateDirection::Reverse {
308 self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0)
311 } else {
312 self.angle.to_computed_value(context)
313 },
314 }
315 }
316
317 #[inline]
318 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
319 OffsetRotate {
320 direction: if computed.auto {
321 OffsetRotateDirection::Auto
322 } else {
323 OffsetRotateDirection::None
324 },
325 angle: ToComputedValue::from_computed_value(&computed.angle),
326 }
327 }
328}