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(
243    Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
244)]
245pub struct OffsetRotate {
246    #[css(skip_if = "OffsetRotateDirection::is_none")]
248    direction: OffsetRotateDirection,
249    #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")]
255    angle: Angle,
256}
257
258impl OffsetRotate {
259    #[inline]
261    pub fn auto() -> Self {
262        OffsetRotate {
263            direction: OffsetRotateDirection::Auto,
264            angle: Angle::zero(),
265        }
266    }
267
268    #[inline]
270    pub fn is_auto(&self) -> bool {
271        self.direction == OffsetRotateDirection::Auto && self.angle.is_zero()
272    }
273}
274
275impl Parse for OffsetRotate {
276    fn parse<'i, 't>(
277        context: &ParserContext,
278        input: &mut Parser<'i, 't>,
279    ) -> Result<Self, ParseError<'i>> {
280        let location = input.current_source_location();
281        let mut direction = input.try_parse(OffsetRotateDirection::parse);
282        let angle = input.try_parse(|i| Angle::parse(context, i));
283        if direction.is_err() {
284            direction = input.try_parse(OffsetRotateDirection::parse);
287        }
288
289        if direction.is_err() && angle.is_err() {
290            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
291        }
292
293        Ok(OffsetRotate {
294            direction: direction.unwrap_or(OffsetRotateDirection::None),
295            angle: angle.unwrap_or(Zero::zero()),
296        })
297    }
298}
299
300impl ToComputedValue for OffsetRotate {
301    type ComputedValue = ComputedOffsetRotate;
302
303    #[inline]
304    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
305        use crate::values::computed::Angle as ComputedAngle;
306
307        ComputedOffsetRotate {
308            auto: !self.direction.is_none(),
309            angle: if self.direction == OffsetRotateDirection::Reverse {
310                self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0)
313            } else {
314                self.angle.to_computed_value(context)
315            },
316        }
317    }
318
319    #[inline]
320    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
321        OffsetRotate {
322            direction: if computed.auto {
323                OffsetRotateDirection::Auto
324            } else {
325                OffsetRotateDirection::None
326            },
327            angle: ToComputedValue::from_computed_value(&computed.angle),
328        }
329    }
330}