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