transform.rs (23845B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Specified types for CSS values that are related to transformations. 6 7 use crate::derives::*; 8 use crate::parser::{Parse, ParserContext}; 9 use crate::values::computed::{Context, LengthPercentage as ComputedLengthPercentage}; 10 use crate::values::computed::{Percentage as ComputedPercentage, ToComputedValue}; 11 use crate::values::generics::transform as generic; 12 use crate::values::generics::transform::{Matrix, Matrix3D}; 13 use crate::values::specified::position::{ 14 HorizontalPositionKeyword, Side, VerticalPositionKeyword, 15 }; 16 use crate::values::specified::{ 17 self, AllowQuirks, Angle, Integer, Length, LengthPercentage, Number, NumberOrPercentage, 18 }; 19 use crate::Zero; 20 use cssparser::{match_ignore_ascii_case, Parser}; 21 use style_traits::{ParseError, StyleParseErrorKind}; 22 23 pub use crate::values::generics::transform::TransformStyle; 24 25 /// A single operation in a specified CSS `transform` 26 pub type TransformOperation = 27 generic::TransformOperation<Angle, Number, Length, Integer, LengthPercentage>; 28 29 /// A specified CSS `transform` 30 pub type Transform = generic::Transform<TransformOperation>; 31 32 /// The specified value of a CSS `<transform-origin>` 33 pub type TransformOrigin = generic::TransformOrigin< 34 OriginComponent<HorizontalPositionKeyword>, 35 OriginComponent<VerticalPositionKeyword>, 36 Length, 37 >; 38 39 #[cfg(feature = "gecko")] 40 fn all_transform_boxes_are_enabled(_context: &ParserContext) -> bool { 41 true 42 } 43 44 #[cfg(feature = "servo")] 45 fn all_transform_boxes_are_enabled(_context: &ParserContext) -> bool { 46 false 47 } 48 49 /// The specified value of `transform-box`. 50 /// https://drafts.csswg.org/css-transforms-1/#transform-box 51 // Note: Once we ship everything, we can drop this and just use single_keyword for tranform-box. 52 #[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)] 73 pub 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 83 impl TransformOrigin { 84 /// Returns the initial specified value for `transform-origin`. 85 #[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 /// Returns the `0 0` value. 95 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 /// Whether to allow unitless values for perspective in prefixed transform properties. 105 /// 106 /// See: https://github.com/whatwg/compat/issues/100 107 #[allow(missing_docs)] 108 pub enum AllowUnitlessPerspective { 109 No, 110 Yes, 111 } 112 113 impl Transform { 114 /// Parse the transform property value, allowing unitless perspective values. 115 /// 116 /// This is used for `-webkit-transform` which allows unitless values for perspective. 117 #[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 /// Internal parse function for deciding if we wish to accept prefixed values or not 125 /// 126 /// `transform` allows unitless zero angles as an exception, see: 127 /// https://github.com/w3c/csswg-drafts/issues/1162 128 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 // Standard matrix parsing. 158 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 // Standard matrix3d parsing. 189 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 // TODO(gw): Check that the axis can be normalized. 286 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 335 impl 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 /// The specified value of a component of a CSS `<transform-origin>`. 345 #[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] 346 pub enum OriginComponent<S> { 347 /// `center` 348 Center, 349 /// `<length-percentage>` 350 Length(LengthPercentage), 351 /// `<side>` 352 Side(S), 353 } 354 355 impl 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 412 impl<S> ToComputedValue for OriginComponent<S> 413 where 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 436 impl<S> OriginComponent<S> { 437 /// `0%` 438 pub fn zero() -> Self { 439 OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage::zero())) 440 } 441 } 442 443 /// A specified CSS `rotate` 444 pub type Rotate = generic::Rotate<Number, Angle>; 445 446 impl 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 // Parse <angle> or [ x | y | z | <number>{3} ] && <angle>. 456 // 457 // The rotate axis and angle could be in any order, so we parse angle twice to cover 458 // two cases. i.e. `<number>{3} <angle>` or `<angle> <number>{3}` 459 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 492 /// A specified CSS `translate` 493 pub type Translate = generic::Translate<LengthPercentage, Length>; 494 495 impl 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 // 'translate: <length-percentage> <length-percentage> <length>' 508 return Ok(generic::Translate::Translate(tx, ty, tz)); 509 } 510 511 // translate: <length-percentage> <length-percentage>' 512 return Ok(generic::Translate::Translate( 513 tx, 514 ty, 515 specified::Length::zero(), 516 )); 517 } 518 519 // 'translate: <length-percentage> ' 520 Ok(generic::Translate::Translate( 521 tx, 522 specified::LengthPercentage::zero(), 523 specified::Length::zero(), 524 )) 525 } 526 } 527 528 /// A specified CSS `scale` 529 pub type Scale = generic::Scale<Number>; 530 531 impl Parse for Scale { 532 /// Scale accepts <number> | <percentage>, so we parse it as NumberOrPercentage, 533 /// and then convert into an Number if it's a Percentage. 534 /// https://github.com/w3c/csswg-drafts/pull/4396 535 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 // 'scale: <number> <number> <number>' 548 return Ok(generic::Scale::Scale(sx, sy, sz.to_number())); 549 } 550 551 // 'scale: <number> <number>' 552 return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0))); 553 } 554 555 // 'scale: <number>' 556 Ok(generic::Scale::Scale(sx, sx, Number::new(1.0))) 557 } 558 }