1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::values::computed::{Context, LengthPercentage as ComputedLengthPercentage};
10use crate::values::computed::{Percentage as ComputedPercentage, ToComputedValue};
11use crate::values::generics::transform as generic;
12use crate::values::generics::transform::{Matrix, Matrix3D};
13use crate::values::specified::position::{
14 HorizontalPositionKeyword, Side, VerticalPositionKeyword,
15};
16use crate::values::specified::{
17 self, AllowQuirks, Angle, Integer, Length, LengthPercentage, Number, NumberOrPercentage,
18};
19use crate::Zero;
20use cssparser::{match_ignore_ascii_case, Parser};
21use style_traits::{ParseError, StyleParseErrorKind};
22
23pub use crate::values::generics::transform::TransformStyle;
24
25pub type TransformOperation =
27 generic::TransformOperation<Angle, Number, Length, Integer, LengthPercentage>;
28
29pub type Transform = generic::Transform<TransformOperation>;
31
32pub type TransformOrigin = generic::TransformOrigin<
34 OriginComponent<HorizontalPositionKeyword>,
35 OriginComponent<VerticalPositionKeyword>,
36 Length,
37>;
38
39#[cfg(feature = "gecko")]
40fn all_transform_boxes_are_enabled(_context: &ParserContext) -> bool {
41 true
42}
43
44#[cfg(feature = "servo")]
45fn all_transform_boxes_are_enabled(_context: &ParserContext) -> bool {
46 false
47}
48
49#[allow(missing_docs)]
53#[derive(
54 Animate,
55 Clone,
56 ComputeSquaredDistance,
57 Copy,
58 Debug,
59 Deserialize,
60 MallocSizeOf,
61 Parse,
62 PartialEq,
63 Serialize,
64 SpecifiedValueInfo,
65 ToAnimatedValue,
66 ToComputedValue,
67 ToCss,
68 ToResolvedValue,
69 ToShmem,
70 ToTyped,
71)]
72#[repr(u8)]
73pub enum TransformBox {
74 #[parse(condition = "all_transform_boxes_are_enabled")]
75 ContentBox,
76 BorderBox,
77 FillBox,
78 #[parse(condition = "all_transform_boxes_are_enabled")]
79 StrokeBox,
80 ViewBox,
81}
82
83impl TransformOrigin {
84 #[inline]
86 pub fn initial_value() -> Self {
87 Self::new(
88 OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))),
89 OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))),
90 Length::zero(),
91 )
92 }
93
94 pub fn zero_zero() -> Self {
96 Self::new(
97 OriginComponent::Length(LengthPercentage::zero()),
98 OriginComponent::Length(LengthPercentage::zero()),
99 Length::zero(),
100 )
101 }
102}
103
104#[allow(missing_docs)]
108pub enum AllowUnitlessPerspective {
109 No,
110 Yes,
111}
112
113impl Transform {
114 #[inline]
118 pub(crate) fn parse_legacy<'i, 't>(
119 context: &ParserContext,
120 input: &mut Parser<'i, 't>,
121 ) -> Result<Self, ParseError<'i>> {
122 Self::parse_internal(context, input, AllowUnitlessPerspective::Yes)
123 }
124 fn parse_internal<'i, 't>(
129 context: &ParserContext,
130 input: &mut Parser<'i, 't>,
131 allow_unitless_perspective: AllowUnitlessPerspective,
132 ) -> Result<Self, ParseError<'i>> {
133 use style_traits::{Separator, Space};
134
135 if input
136 .try_parse(|input| input.expect_ident_matching("none"))
137 .is_ok()
138 {
139 return Ok(generic::Transform::none());
140 }
141
142 Ok(generic::Transform(
143 Space::parse(input, |input| {
144 let function = input.expect_function()?.clone();
145 input.parse_nested_block(|input| {
146 let location = input.current_source_location();
147 let result = match_ignore_ascii_case! { &function,
148 "matrix" => {
149 let a = Number::parse(context, input)?;
150 input.expect_comma()?;
151 let b = Number::parse(context, input)?;
152 input.expect_comma()?;
153 let c = Number::parse(context, input)?;
154 input.expect_comma()?;
155 let d = Number::parse(context, input)?;
156 input.expect_comma()?;
157 let e = Number::parse(context, input)?;
159 input.expect_comma()?;
160 let f = Number::parse(context, input)?;
161 Ok(generic::TransformOperation::Matrix(Matrix { a, b, c, d, e, f }))
162 },
163 "matrix3d" => {
164 let m11 = Number::parse(context, input)?;
165 input.expect_comma()?;
166 let m12 = Number::parse(context, input)?;
167 input.expect_comma()?;
168 let m13 = Number::parse(context, input)?;
169 input.expect_comma()?;
170 let m14 = Number::parse(context, input)?;
171 input.expect_comma()?;
172 let m21 = Number::parse(context, input)?;
173 input.expect_comma()?;
174 let m22 = Number::parse(context, input)?;
175 input.expect_comma()?;
176 let m23 = Number::parse(context, input)?;
177 input.expect_comma()?;
178 let m24 = Number::parse(context, input)?;
179 input.expect_comma()?;
180 let m31 = Number::parse(context, input)?;
181 input.expect_comma()?;
182 let m32 = Number::parse(context, input)?;
183 input.expect_comma()?;
184 let m33 = Number::parse(context, input)?;
185 input.expect_comma()?;
186 let m34 = Number::parse(context, input)?;
187 input.expect_comma()?;
188 let m41 = Number::parse(context, input)?;
190 input.expect_comma()?;
191 let m42 = Number::parse(context, input)?;
192 input.expect_comma()?;
193 let m43 = Number::parse(context, input)?;
194 input.expect_comma()?;
195 let m44 = Number::parse(context, input)?;
196 Ok(generic::TransformOperation::Matrix3D(Matrix3D {
197 m11, m12, m13, m14,
198 m21, m22, m23, m24,
199 m31, m32, m33, m34,
200 m41, m42, m43, m44,
201 }))
202 },
203 "translate" => {
204 let sx = specified::LengthPercentage::parse(context, input)?;
205 if input.try_parse(|input| input.expect_comma()).is_ok() {
206 let sy = specified::LengthPercentage::parse(context, input)?;
207 Ok(generic::TransformOperation::Translate(sx, sy))
208 } else {
209 Ok(generic::TransformOperation::Translate(sx, Zero::zero()))
210 }
211 },
212 "translatex" => {
213 let tx = specified::LengthPercentage::parse(context, input)?;
214 Ok(generic::TransformOperation::TranslateX(tx))
215 },
216 "translatey" => {
217 let ty = specified::LengthPercentage::parse(context, input)?;
218 Ok(generic::TransformOperation::TranslateY(ty))
219 },
220 "translatez" => {
221 let tz = specified::Length::parse(context, input)?;
222 Ok(generic::TransformOperation::TranslateZ(tz))
223 },
224 "translate3d" => {
225 let tx = specified::LengthPercentage::parse(context, input)?;
226 input.expect_comma()?;
227 let ty = specified::LengthPercentage::parse(context, input)?;
228 input.expect_comma()?;
229 let tz = specified::Length::parse(context, input)?;
230 Ok(generic::TransformOperation::Translate3D(tx, ty, tz))
231 },
232 "scale" => {
233 let sx = NumberOrPercentage::parse(context, input)?.to_number();
234 if input.try_parse(|input| input.expect_comma()).is_ok() {
235 let sy = NumberOrPercentage::parse(context, input)?.to_number();
236 Ok(generic::TransformOperation::Scale(sx, sy))
237 } else {
238 Ok(generic::TransformOperation::Scale(sx, sx))
239 }
240 },
241 "scalex" => {
242 let sx = NumberOrPercentage::parse(context, input)?.to_number();
243 Ok(generic::TransformOperation::ScaleX(sx))
244 },
245 "scaley" => {
246 let sy = NumberOrPercentage::parse(context, input)?.to_number();
247 Ok(generic::TransformOperation::ScaleY(sy))
248 },
249 "scalez" => {
250 let sz = NumberOrPercentage::parse(context, input)?.to_number();
251 Ok(generic::TransformOperation::ScaleZ(sz))
252 },
253 "scale3d" => {
254 let sx = NumberOrPercentage::parse(context, input)?.to_number();
255 input.expect_comma()?;
256 let sy = NumberOrPercentage::parse(context, input)?.to_number();
257 input.expect_comma()?;
258 let sz = NumberOrPercentage::parse(context, input)?.to_number();
259 Ok(generic::TransformOperation::Scale3D(sx, sy, sz))
260 },
261 "rotate" => {
262 let theta = specified::Angle::parse_with_unitless(context, input)?;
263 Ok(generic::TransformOperation::Rotate(theta))
264 },
265 "rotatex" => {
266 let theta = specified::Angle::parse_with_unitless(context, input)?;
267 Ok(generic::TransformOperation::RotateX(theta))
268 },
269 "rotatey" => {
270 let theta = specified::Angle::parse_with_unitless(context, input)?;
271 Ok(generic::TransformOperation::RotateY(theta))
272 },
273 "rotatez" => {
274 let theta = specified::Angle::parse_with_unitless(context, input)?;
275 Ok(generic::TransformOperation::RotateZ(theta))
276 },
277 "rotate3d" => {
278 let ax = Number::parse(context, input)?;
279 input.expect_comma()?;
280 let ay = Number::parse(context, input)?;
281 input.expect_comma()?;
282 let az = Number::parse(context, input)?;
283 input.expect_comma()?;
284 let theta = specified::Angle::parse_with_unitless(context, input)?;
285 Ok(generic::TransformOperation::Rotate3D(ax, ay, az, theta))
287 },
288 "skew" => {
289 let ax = specified::Angle::parse_with_unitless(context, input)?;
290 if input.try_parse(|input| input.expect_comma()).is_ok() {
291 let ay = specified::Angle::parse_with_unitless(context, input)?;
292 Ok(generic::TransformOperation::Skew(ax, ay))
293 } else {
294 Ok(generic::TransformOperation::Skew(ax, Zero::zero()))
295 }
296 },
297 "skewx" => {
298 let theta = specified::Angle::parse_with_unitless(context, input)?;
299 Ok(generic::TransformOperation::SkewX(theta))
300 },
301 "skewy" => {
302 let theta = specified::Angle::parse_with_unitless(context, input)?;
303 Ok(generic::TransformOperation::SkewY(theta))
304 },
305 "perspective" => {
306 let p = match input.try_parse(|input| {
307 if matches!(allow_unitless_perspective, AllowUnitlessPerspective::Yes) {
308 specified::Length::parse_non_negative_quirky(context, input, AllowQuirks::Always)
309 } else {
310 specified::Length::parse_non_negative(context, input)
311 }
312 }) {
313 Ok(p) => generic::PerspectiveFunction::Length(p),
314 Err(..) => {
315 input.expect_ident_matching("none")?;
316 generic::PerspectiveFunction::None
317 }
318 };
319 Ok(generic::TransformOperation::Perspective(p))
320 },
321 _ => Err(()),
322 };
323 result.map_err(|()| {
324 location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(
325 function.clone(),
326 ))
327 })
328 })
329 })?
330 .into(),
331 ))
332 }
333}
334
335impl Parse for Transform {
336 fn parse<'i, 't>(
337 context: &ParserContext,
338 input: &mut Parser<'i, 't>,
339 ) -> Result<Self, ParseError<'i>> {
340 Transform::parse_internal(context, input, AllowUnitlessPerspective::No)
341 }
342}
343
344#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
346pub enum OriginComponent<S> {
347 Center,
349 Length(LengthPercentage),
351 Side(S),
353}
354
355impl Parse for TransformOrigin {
356 fn parse<'i, 't>(
357 context: &ParserContext,
358 input: &mut Parser<'i, 't>,
359 ) -> Result<Self, ParseError<'i>> {
360 let parse_depth = |input: &mut Parser| {
361 input
362 .try_parse(|i| Length::parse(context, i))
363 .unwrap_or(Length::zero())
364 };
365 match input.try_parse(|i| OriginComponent::parse(context, i)) {
366 Ok(x_origin @ OriginComponent::Center) => {
367 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
368 let depth = parse_depth(input);
369 return Ok(Self::new(x_origin, y_origin, depth));
370 }
371 let y_origin = OriginComponent::Center;
372 if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
373 let x_origin = OriginComponent::Side(x_keyword);
374 let depth = parse_depth(input);
375 return Ok(Self::new(x_origin, y_origin, depth));
376 }
377 let depth = Length::from_px(0.);
378 return Ok(Self::new(x_origin, y_origin, depth));
379 },
380 Ok(x_origin) => {
381 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
382 let depth = parse_depth(input);
383 return Ok(Self::new(x_origin, y_origin, depth));
384 }
385 let y_origin = OriginComponent::Center;
386 let depth = Length::from_px(0.);
387 return Ok(Self::new(x_origin, y_origin, depth));
388 },
389 Err(_) => {},
390 }
391 let y_keyword = VerticalPositionKeyword::parse(input)?;
392 let y_origin = OriginComponent::Side(y_keyword);
393 if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
394 let x_origin = OriginComponent::Side(x_keyword);
395 let depth = parse_depth(input);
396 return Ok(Self::new(x_origin, y_origin, depth));
397 }
398 if input
399 .try_parse(|i| i.expect_ident_matching("center"))
400 .is_ok()
401 {
402 let x_origin = OriginComponent::Center;
403 let depth = parse_depth(input);
404 return Ok(Self::new(x_origin, y_origin, depth));
405 }
406 let x_origin = OriginComponent::Center;
407 let depth = Length::from_px(0.);
408 Ok(Self::new(x_origin, y_origin, depth))
409 }
410}
411
412impl<S> ToComputedValue for OriginComponent<S>
413where
414 S: Side,
415{
416 type ComputedValue = ComputedLengthPercentage;
417
418 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
419 match *self {
420 OriginComponent::Center => {
421 ComputedLengthPercentage::new_percent(ComputedPercentage(0.5))
422 },
423 OriginComponent::Length(ref length) => length.to_computed_value(context),
424 OriginComponent::Side(ref keyword) => {
425 let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
426 ComputedLengthPercentage::new_percent(p)
427 },
428 }
429 }
430
431 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
432 OriginComponent::Length(ToComputedValue::from_computed_value(computed))
433 }
434}
435
436impl<S> OriginComponent<S> {
437 pub fn zero() -> Self {
439 OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage::zero()))
440 }
441}
442
443pub type Rotate = generic::Rotate<Number, Angle>;
445
446impl Parse for Rotate {
447 fn parse<'i, 't>(
448 context: &ParserContext,
449 input: &mut Parser<'i, 't>,
450 ) -> Result<Self, ParseError<'i>> {
451 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
452 return Ok(generic::Rotate::None);
453 }
454
455 let angle = input
460 .try_parse(|i| specified::Angle::parse(context, i))
461 .ok();
462 let axis = input
463 .try_parse(|i| {
464 Ok(try_match_ident_ignore_ascii_case! { i,
465 "x" => (Number::new(1.), Number::new(0.), Number::new(0.)),
466 "y" => (Number::new(0.), Number::new(1.), Number::new(0.)),
467 "z" => (Number::new(0.), Number::new(0.), Number::new(1.)),
468 })
469 })
470 .or_else(|_: ParseError| -> Result<_, ParseError> {
471 input.try_parse(|i| {
472 Ok((
473 Number::parse(context, i)?,
474 Number::parse(context, i)?,
475 Number::parse(context, i)?,
476 ))
477 })
478 })
479 .ok();
480 let angle = match angle {
481 Some(a) => a,
482 None => specified::Angle::parse(context, input)?,
483 };
484
485 Ok(match axis {
486 Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle),
487 None => generic::Rotate::Rotate(angle),
488 })
489 }
490}
491
492pub type Translate = generic::Translate<LengthPercentage, Length>;
494
495impl Parse for Translate {
496 fn parse<'i, 't>(
497 context: &ParserContext,
498 input: &mut Parser<'i, 't>,
499 ) -> Result<Self, ParseError<'i>> {
500 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
501 return Ok(generic::Translate::None);
502 }
503
504 let tx = specified::LengthPercentage::parse(context, input)?;
505 if let Ok(ty) = input.try_parse(|i| specified::LengthPercentage::parse(context, i)) {
506 if let Ok(tz) = input.try_parse(|i| specified::Length::parse(context, i)) {
507 return Ok(generic::Translate::Translate(tx, ty, tz));
509 }
510
511 return Ok(generic::Translate::Translate(
513 tx,
514 ty,
515 specified::Length::zero(),
516 ));
517 }
518
519 Ok(generic::Translate::Translate(
521 tx,
522 specified::LengthPercentage::zero(),
523 specified::Length::zero(),
524 ))
525 }
526}
527
528pub type Scale = generic::Scale<Number>;
530
531impl Parse for Scale {
532 fn parse<'i, 't>(
536 context: &ParserContext,
537 input: &mut Parser<'i, 't>,
538 ) -> Result<Self, ParseError<'i>> {
539 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
540 return Ok(generic::Scale::None);
541 }
542
543 let sx = NumberOrPercentage::parse(context, input)?.to_number();
544 if let Ok(sy) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
545 let sy = sy.to_number();
546 if let Ok(sz) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
547 return Ok(generic::Scale::Scale(sx, sy, sz.to_number()));
549 }
550
551 return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0)));
553 }
554
555 Ok(generic::Scale::Scale(sx, sx, Number::new(1.0)))
557 }
558}