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