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