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(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
77 input.expect_function_matching("ray")?;
78 input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
79 }
80}
81
82impl RayFunction {
83 fn parse_function_arguments(
85 context: &ParserContext,
86 input: &mut Parser,
87 ) -> Result<Self, ParseError> {
88 use crate::values::specified::PositionOrAuto;
89
90 let mut angle = None;
91 let mut size = None;
92 let mut contain = false;
93 let mut position = None;
94 loop {
95 if angle.is_none() {
96 angle = input.try_parse(|i| Angle::parse(context, i)).ok();
97 }
98
99 if size.is_none() {
100 size = input.try_parse(generics::RaySize::parse).ok();
101 if size.is_some() {
102 continue;
103 }
104 }
105
106 if !contain {
107 contain = input
108 .try_parse(|i| i.expect_ident_matching("contain"))
109 .is_ok();
110 if contain {
111 continue;
112 }
113 }
114
115 if position.is_none() {
116 if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
117 let pos = Position::parse(context, input)?;
118 position = Some(PositionOrAuto::Position(pos));
119 }
120
121 if position.is_some() {
122 continue;
123 }
124 }
125 break;
126 }
127
128 if angle.is_none() {
129 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
130 }
131
132 Ok(RayFunction {
133 angle: angle.unwrap(),
134 size: size.unwrap_or(generics::RaySize::ClosestSide),
136 contain,
137 position: position.unwrap_or(PositionOrAuto::auto()),
138 })
139 }
140}
141
142impl Parse for OffsetPathFunction {
143 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
144 use crate::values::specified::basic_shape::{AllowedBasicShapes, ShapeType};
145
146 if let Ok(ray) = input.try_parse(|i| RayFunction::parse(context, i)) {
149 return Ok(OffsetPathFunction::Ray(ray));
150 }
151
152 if crate::pref!("layout.css.motion-path-url.enabled") {
153 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
154 return Ok(OffsetPathFunction::Url(url));
155 }
156 }
157
158 BasicShape::parse(context, input, AllowedBasicShapes::ALL, ShapeType::Outline)
159 .map(OffsetPathFunction::Shape)
160 }
161}
162
163impl Parse for OffsetPath {
164 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
165 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
167 return Ok(OffsetPath::none());
168 }
169
170 let mut path = None;
171 let mut coord_box = None;
172 loop {
173 if path.is_none() {
174 path = input
175 .try_parse(|i| OffsetPathFunction::parse(context, i))
176 .ok();
177 }
178
179 if coord_box.is_none() {
180 coord_box = input.try_parse(CoordBox::parse).ok();
181 if coord_box.is_some() {
182 continue;
183 }
184 }
185 break;
186 }
187
188 if let Some(p) = path {
189 return Ok(OffsetPath::OffsetPath {
190 path: Box::new(p),
191 coord_box: coord_box.unwrap_or(CoordBox::BorderBox),
192 });
193 }
194
195 match coord_box {
196 Some(c) => Ok(OffsetPath::CoordBox(c)),
197 None => Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
198 }
199 }
200}
201
202#[derive(
204 Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
205)]
206#[repr(u8)]
207pub enum OffsetRotateDirection {
208 #[css(skip)]
210 None,
211 Auto,
213 Reverse,
215}
216
217impl OffsetRotateDirection {
218 #[inline]
220 fn is_none(&self) -> bool {
221 *self == OffsetRotateDirection::None
222 }
223}
224
225#[inline]
226fn direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool {
227 !direction.is_none() && angle.is_zero()
228}
229
230#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
235pub struct OffsetRotate {
236 #[css(skip_if = "OffsetRotateDirection::is_none")]
238 direction: OffsetRotateDirection,
239 #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")]
245 angle: Angle,
246}
247
248impl OffsetRotate {
249 #[inline]
251 pub fn auto() -> Self {
252 OffsetRotate {
253 direction: OffsetRotateDirection::Auto,
254 angle: Angle::zero(),
255 }
256 }
257
258 #[inline]
260 pub fn is_auto(&self) -> bool {
261 self.direction == OffsetRotateDirection::Auto && self.angle.is_zero()
262 }
263}
264
265impl Parse for OffsetRotate {
266 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
267 let mut direction = input.try_parse(OffsetRotateDirection::parse);
268 let angle = input.try_parse(|i| Angle::parse(context, i));
269 if direction.is_err() {
270 direction = input.try_parse(OffsetRotateDirection::parse);
273 }
274
275 if direction.is_err() && angle.is_err() {
276 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
277 }
278
279 Ok(OffsetRotate {
280 direction: direction.unwrap_or(OffsetRotateDirection::None),
281 angle: angle.unwrap_or(Zero::zero()),
282 })
283 }
284}
285
286impl ToComputedValue for OffsetRotate {
287 type ComputedValue = ComputedOffsetRotate;
288
289 #[inline]
290 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
291 use crate::values::computed::Angle as ComputedAngle;
292
293 ComputedOffsetRotate {
294 auto: !self.direction.is_none(),
295 angle: if self.direction == OffsetRotateDirection::Reverse {
296 self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0)
299 } else {
300 self.angle.to_computed_value(context)
301 },
302 }
303 }
304
305 #[inline]
306 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
307 OffsetRotate {
308 direction: if computed.auto {
309 OffsetRotateDirection::Auto
310 } else {
311 OffsetRotateDirection::None
312 },
313 angle: ToComputedValue::from_computed_value(&computed.angle),
314 }
315 }
316}