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(
120 context: &ParserContext,
121 input: &mut Parser,
122 ) -> Result<Self, ParseError> {
123 Self::parse_internal(context, input, AllowUnitlessPerspective::Yes)
124 }
125
126 fn parse_internal(
131 context: &ParserContext,
132 input: &mut Parser,
133 allow_unitless_perspective: AllowUnitlessPerspective,
134 ) -> Result<Self, ParseError> {
135 use style_traits::{Separator, Space};
136
137 if input
138 .try_parse(|input| input.expect_ident_matching("none"))
139 .is_ok()
140 {
141 return Ok(generic::Transform::none());
142 }
143
144 Ok(generic::Transform(
145 Space::parse(input, |input| {
146 let function = input.expect_function()?.clone();
147 input.parse_nested_block(|input| {
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(ParseError::custom(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(ParseError::custom(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(ParseError::custom(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(ParseError::custom(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(ParseError::custom(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(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
269 };
270 input.expect_comma()?;
271 let Some(sy) = NumberOrPercentage::parse(context, input)?.to_number() else {
272 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
273 };
274 input.expect_comma()?;
275 let Some(sz) = NumberOrPercentage::parse(context, input)?.to_number() else {
276 return Err(ParseError::custom(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 ParseError::custom(StyleParseErrorKind::UnexpectedFunction)
344 })
345 })
346 })?
347 .into(),
348 ))
349 }
350}
351
352impl Parse for Transform {
353 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
354 Transform::parse_internal(context, input, AllowUnitlessPerspective::No)
355 }
356}
357
358#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
360pub enum OriginComponent<S> {
361 Center,
363 Length(LengthPercentage),
365 Side(S),
367}
368
369impl Parse for TransformOrigin {
370 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
371 let parse_depth = |input: &mut Parser| {
372 input
373 .try_parse(|i| Length::parse(context, i))
374 .unwrap_or(Length::zero())
375 };
376 match input.try_parse(|i| OriginComponent::parse(context, i)) {
377 Ok(x_origin @ OriginComponent::Center) => {
378 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
379 let depth = parse_depth(input);
380 return Ok(Self::new(x_origin, y_origin, depth));
381 }
382 let y_origin = OriginComponent::Center;
383 if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
384 let x_origin = OriginComponent::Side(x_keyword);
385 let depth = parse_depth(input);
386 return Ok(Self::new(x_origin, y_origin, depth));
387 }
388 let depth = Length::from_px(0.);
389 return Ok(Self::new(x_origin, y_origin, depth));
390 },
391 Ok(x_origin) => {
392 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
393 let depth = parse_depth(input);
394 return Ok(Self::new(x_origin, y_origin, depth));
395 }
396 let y_origin = OriginComponent::Center;
397 let depth = Length::from_px(0.);
398 return Ok(Self::new(x_origin, y_origin, depth));
399 },
400 Err(_) => {},
401 }
402 let y_keyword = VerticalPositionKeyword::parse(input)?;
403 let y_origin = OriginComponent::Side(y_keyword);
404 if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
405 let x_origin = OriginComponent::Side(x_keyword);
406 let depth = parse_depth(input);
407 return Ok(Self::new(x_origin, y_origin, depth));
408 }
409 if input
410 .try_parse(|i| i.expect_ident_matching("center"))
411 .is_ok()
412 {
413 let x_origin = OriginComponent::Center;
414 let depth = parse_depth(input);
415 return Ok(Self::new(x_origin, y_origin, depth));
416 }
417 let x_origin = OriginComponent::Center;
418 let depth = Length::from_px(0.);
419 Ok(Self::new(x_origin, y_origin, depth))
420 }
421}
422
423impl<S> ToComputedValue for OriginComponent<S>
424where
425 S: Side,
426{
427 type ComputedValue = ComputedLengthPercentage;
428
429 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
430 match *self {
431 OriginComponent::Center => {
432 ComputedLengthPercentage::new_percent(ComputedPercentage(0.5))
433 },
434 OriginComponent::Length(ref length) => length.to_computed_value(context),
435 OriginComponent::Side(ref keyword) => {
436 let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
437 ComputedLengthPercentage::new_percent(p)
438 },
439 }
440 }
441
442 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
443 OriginComponent::Length(ToComputedValue::from_computed_value(computed))
444 }
445}
446
447impl<S> OriginComponent<S> {
448 pub fn zero() -> Self {
450 OriginComponent::Length(LengthPercentage::Percentage(NoCalcPercentage::new(0.)))
451 }
452}
453
454pub type Rotate = generic::Rotate<Number, Angle>;
456
457impl Parse for Rotate {
458 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
459 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
460 return Ok(generic::Rotate::None);
461 }
462
463 let angle = input
468 .try_parse(|i| specified::Angle::parse(context, i))
469 .ok();
470 let axis = input
471 .try_parse(|i| {
472 Ok(try_match_ident_ignore_ascii_case! { i,
473 "x" => (Number::new(1.), Number::new(0.), Number::new(0.)),
474 "y" => (Number::new(0.), Number::new(1.), Number::new(0.)),
475 "z" => (Number::new(0.), Number::new(0.), Number::new(1.)),
476 })
477 })
478 .or_else(|_: ParseError| -> Result<_, ParseError> {
479 input.try_parse(|i| {
480 Ok((
481 Number::parse(context, i)?,
482 Number::parse(context, i)?,
483 Number::parse(context, i)?,
484 ))
485 })
486 })
487 .ok();
488 let angle = match angle {
489 Some(a) => a,
490 None => specified::Angle::parse(context, input)?,
491 };
492
493 Ok(match axis {
494 Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle),
495 None => generic::Rotate::Rotate(angle),
496 })
497 }
498}
499
500pub type Translate = generic::Translate<LengthPercentage, Length>;
502
503impl Parse for Translate {
504 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
505 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
506 return Ok(generic::Translate::None);
507 }
508
509 let tx = specified::LengthPercentage::parse(context, input)?;
510 if let Ok(ty) = input.try_parse(|i| specified::LengthPercentage::parse(context, i)) {
511 if let Ok(tz) = input.try_parse(|i| specified::Length::parse(context, i)) {
512 return Ok(generic::Translate::Translate(tx, ty, tz));
514 }
515
516 return Ok(generic::Translate::Translate(
518 tx,
519 ty,
520 specified::Length::zero(),
521 ));
522 }
523
524 Ok(generic::Translate::Translate(
526 tx,
527 specified::LengthPercentage::zero(),
528 specified::Length::zero(),
529 ))
530 }
531}
532
533#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
535pub struct ScaleFactor(NumberOrPercentage);
536
537impl ScaleFactor {
538 fn one() -> Self {
539 ScaleFactor(NumberOrPercentage::Number(Number::new(1.0)))
540 }
541}
542
543impl Parse for ScaleFactor {
544 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
548 Ok(ScaleFactor(
549 NumberOrPercentage::parse(context, input)?.into_simplified_number(),
550 ))
551 }
552}
553
554impl ToComputedValue for ScaleFactor {
555 type ComputedValue = <Number as ToComputedValue>::ComputedValue;
556
557 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
558 self.0.to_computed_value(context).value()
559 }
560
561 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
562 ScaleFactor(NumberOrPercentage::Number(Number::from_computed_value(
563 computed,
564 )))
565 }
566}
567
568impl ToFloat for ScaleFactor {
569 fn to_f32(&self) -> Result<f32, ()> {
570 match &self.0 {
571 NumberOrPercentage::Number(n) => n.resolve().ok_or(()),
572 NumberOrPercentage::Percentage(p) => p.resolve().ok_or(()),
573 }
574 }
575
576 fn to_f64(&self) -> Result<f64, ()> {
577 self.to_f32().map(|v| v as f64)
578 }
579}
580
581pub type Scale = generic::Scale<ScaleFactor>;
583
584impl Parse for Scale {
585 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
586 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
587 return Ok(generic::Scale::None);
588 }
589
590 let sx = ScaleFactor::parse(context, input)?;
591 if let Ok(sy) = input.try_parse(|i| ScaleFactor::parse(context, i)) {
592 if let Ok(sz) = input.try_parse(|i| ScaleFactor::parse(context, i)) {
593 return Ok(generic::Scale::Scale(sx, sy, sz));
594 }
595 return Ok(generic::Scale::Scale(sx, sy, ScaleFactor::one()));
596 }
597 Ok(generic::Scale::Scale(sx.clone(), sx, ScaleFactor::one()))
598 }
599}