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::parser::{Parse, ParserContext}; 8 use crate::values::computed::{Context, LengthPercentage as ComputedLengthPercentage}; 9 use crate::values::computed::{Percentage as ComputedPercentage, ToComputedValue}; 10 use crate::values::generics::transform as generic; 11 use crate::values::generics::transform::{Matrix, Matrix3D}; 12 use crate::values::specified::position::{ 13 HorizontalPositionKeyword, Side, VerticalPositionKeyword, 14 }; 15 use crate::values::specified::{ 16 self, Angle, Integer, Length, LengthPercentage, Number, NumberOrPercentage, 17 }; 18 use crate::Zero; 19 use cssparser::Parser; 20 use style_traits::{ParseError, StyleParseErrorKind}; 21 22 pub use crate::values::generics::transform::TransformStyle; 23 24 /// A single operation in a specified CSS `transform` 25 pub type TransformOperation = 26 generic::TransformOperation<Angle, Number, Length, Integer, LengthPercentage>; 27 28 /// A specified CSS `transform` 29 pub type Transform = generic::Transform<TransformOperation>; 30 31 /// The specified value of a CSS `<transform-origin>` 32 pub type TransformOrigin = generic::TransformOrigin< 33 OriginComponent<HorizontalPositionKeyword>, 34 OriginComponent<VerticalPositionKeyword>, 35 Length, 36 >; 37 38 impl TransformOrigin { 39 /// Returns the initial specified value for `transform-origin`. 40 #[inline] initial_value() -> Self41 pub fn initial_value() -> Self { 42 Self::new( 43 OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))), 44 OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))), 45 Length::zero(), 46 ) 47 } 48 49 /// Returns the `0 0` value. zero_zero() -> Self50 pub fn zero_zero() -> Self { 51 Self::new( 52 OriginComponent::Length(LengthPercentage::zero()), 53 OriginComponent::Length(LengthPercentage::zero()), 54 Length::zero(), 55 ) 56 } 57 } 58 59 impl Transform { 60 /// Internal parse function for deciding if we wish to accept prefixed values or not 61 /// 62 /// `transform` allows unitless zero angles as an exception, see: 63 /// https://github.com/w3c/csswg-drafts/issues/1162 parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>64 fn parse_internal<'i, 't>( 65 context: &ParserContext, 66 input: &mut Parser<'i, 't>, 67 ) -> Result<Self, ParseError<'i>> { 68 use style_traits::{Separator, Space}; 69 70 if input 71 .try_parse(|input| input.expect_ident_matching("none")) 72 .is_ok() 73 { 74 return Ok(generic::Transform::none()); 75 } 76 77 Ok(generic::Transform( 78 Space::parse(input, |input| { 79 let function = input.expect_function()?.clone(); 80 input.parse_nested_block(|input| { 81 let location = input.current_source_location(); 82 let result = match_ignore_ascii_case! { &function, 83 "matrix" => { 84 let a = Number::parse(context, input)?; 85 input.expect_comma()?; 86 let b = Number::parse(context, input)?; 87 input.expect_comma()?; 88 let c = Number::parse(context, input)?; 89 input.expect_comma()?; 90 let d = Number::parse(context, input)?; 91 input.expect_comma()?; 92 // Standard matrix parsing. 93 let e = Number::parse(context, input)?; 94 input.expect_comma()?; 95 let f = Number::parse(context, input)?; 96 Ok(generic::TransformOperation::Matrix(Matrix { a, b, c, d, e, f })) 97 }, 98 "matrix3d" => { 99 let m11 = Number::parse(context, input)?; 100 input.expect_comma()?; 101 let m12 = Number::parse(context, input)?; 102 input.expect_comma()?; 103 let m13 = Number::parse(context, input)?; 104 input.expect_comma()?; 105 let m14 = Number::parse(context, input)?; 106 input.expect_comma()?; 107 let m21 = Number::parse(context, input)?; 108 input.expect_comma()?; 109 let m22 = Number::parse(context, input)?; 110 input.expect_comma()?; 111 let m23 = Number::parse(context, input)?; 112 input.expect_comma()?; 113 let m24 = Number::parse(context, input)?; 114 input.expect_comma()?; 115 let m31 = Number::parse(context, input)?; 116 input.expect_comma()?; 117 let m32 = Number::parse(context, input)?; 118 input.expect_comma()?; 119 let m33 = Number::parse(context, input)?; 120 input.expect_comma()?; 121 let m34 = Number::parse(context, input)?; 122 input.expect_comma()?; 123 // Standard matrix3d parsing. 124 let m41 = Number::parse(context, input)?; 125 input.expect_comma()?; 126 let m42 = Number::parse(context, input)?; 127 input.expect_comma()?; 128 let m43 = Number::parse(context, input)?; 129 input.expect_comma()?; 130 let m44 = Number::parse(context, input)?; 131 Ok(generic::TransformOperation::Matrix3D(Matrix3D { 132 m11, m12, m13, m14, 133 m21, m22, m23, m24, 134 m31, m32, m33, m34, 135 m41, m42, m43, m44, 136 })) 137 }, 138 "translate" => { 139 let sx = specified::LengthPercentage::parse(context, input)?; 140 if input.try_parse(|input| input.expect_comma()).is_ok() { 141 let sy = specified::LengthPercentage::parse(context, input)?; 142 Ok(generic::TransformOperation::Translate(sx, sy)) 143 } else { 144 Ok(generic::TransformOperation::Translate(sx, Zero::zero())) 145 } 146 }, 147 "translatex" => { 148 let tx = specified::LengthPercentage::parse(context, input)?; 149 Ok(generic::TransformOperation::TranslateX(tx)) 150 }, 151 "translatey" => { 152 let ty = specified::LengthPercentage::parse(context, input)?; 153 Ok(generic::TransformOperation::TranslateY(ty)) 154 }, 155 "translatez" => { 156 let tz = specified::Length::parse(context, input)?; 157 Ok(generic::TransformOperation::TranslateZ(tz)) 158 }, 159 "translate3d" => { 160 let tx = specified::LengthPercentage::parse(context, input)?; 161 input.expect_comma()?; 162 let ty = specified::LengthPercentage::parse(context, input)?; 163 input.expect_comma()?; 164 let tz = specified::Length::parse(context, input)?; 165 Ok(generic::TransformOperation::Translate3D(tx, ty, tz)) 166 }, 167 "scale" => { 168 let sx = NumberOrPercentage::parse(context, input)?.to_number(); 169 if input.try_parse(|input| input.expect_comma()).is_ok() { 170 let sy = NumberOrPercentage::parse(context, input)?.to_number(); 171 Ok(generic::TransformOperation::Scale(sx, sy)) 172 } else { 173 Ok(generic::TransformOperation::Scale(sx, sx)) 174 } 175 }, 176 "scalex" => { 177 let sx = NumberOrPercentage::parse(context, input)?.to_number(); 178 Ok(generic::TransformOperation::ScaleX(sx)) 179 }, 180 "scaley" => { 181 let sy = NumberOrPercentage::parse(context, input)?.to_number(); 182 Ok(generic::TransformOperation::ScaleY(sy)) 183 }, 184 "scalez" => { 185 let sz = NumberOrPercentage::parse(context, input)?.to_number(); 186 Ok(generic::TransformOperation::ScaleZ(sz)) 187 }, 188 "scale3d" => { 189 let sx = NumberOrPercentage::parse(context, input)?.to_number(); 190 input.expect_comma()?; 191 let sy = NumberOrPercentage::parse(context, input)?.to_number(); 192 input.expect_comma()?; 193 let sz = NumberOrPercentage::parse(context, input)?.to_number(); 194 Ok(generic::TransformOperation::Scale3D(sx, sy, sz)) 195 }, 196 "rotate" => { 197 let theta = specified::Angle::parse_with_unitless(context, input)?; 198 Ok(generic::TransformOperation::Rotate(theta)) 199 }, 200 "rotatex" => { 201 let theta = specified::Angle::parse_with_unitless(context, input)?; 202 Ok(generic::TransformOperation::RotateX(theta)) 203 }, 204 "rotatey" => { 205 let theta = specified::Angle::parse_with_unitless(context, input)?; 206 Ok(generic::TransformOperation::RotateY(theta)) 207 }, 208 "rotatez" => { 209 let theta = specified::Angle::parse_with_unitless(context, input)?; 210 Ok(generic::TransformOperation::RotateZ(theta)) 211 }, 212 "rotate3d" => { 213 let ax = Number::parse(context, input)?; 214 input.expect_comma()?; 215 let ay = Number::parse(context, input)?; 216 input.expect_comma()?; 217 let az = Number::parse(context, input)?; 218 input.expect_comma()?; 219 let theta = specified::Angle::parse_with_unitless(context, input)?; 220 // TODO(gw): Check that the axis can be normalized. 221 Ok(generic::TransformOperation::Rotate3D(ax, ay, az, theta)) 222 }, 223 "skew" => { 224 let ax = specified::Angle::parse_with_unitless(context, input)?; 225 if input.try_parse(|input| input.expect_comma()).is_ok() { 226 let ay = specified::Angle::parse_with_unitless(context, input)?; 227 Ok(generic::TransformOperation::Skew(ax, ay)) 228 } else { 229 Ok(generic::TransformOperation::Skew(ax, Zero::zero())) 230 } 231 }, 232 "skewx" => { 233 let theta = specified::Angle::parse_with_unitless(context, input)?; 234 Ok(generic::TransformOperation::SkewX(theta)) 235 }, 236 "skewy" => { 237 let theta = specified::Angle::parse_with_unitless(context, input)?; 238 Ok(generic::TransformOperation::SkewY(theta)) 239 }, 240 "perspective" => { 241 let p = match input.try_parse(|input| specified::Length::parse_non_negative(context, input)) { 242 Ok(p) => generic::PerspectiveFunction::Length(p), 243 Err(..) => { 244 input.expect_ident_matching("none")?; 245 generic::PerspectiveFunction::None 246 } 247 }; 248 Ok(generic::TransformOperation::Perspective(p)) 249 }, 250 _ => Err(()), 251 }; 252 result.map_err(|()| { 253 location.new_custom_error(StyleParseErrorKind::UnexpectedFunction( 254 function.clone(), 255 )) 256 }) 257 }) 258 })? 259 .into(), 260 )) 261 } 262 } 263 264 impl Parse for Transform { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>265 fn parse<'i, 't>( 266 context: &ParserContext, 267 input: &mut Parser<'i, 't>, 268 ) -> Result<Self, ParseError<'i>> { 269 Transform::parse_internal(context, input) 270 } 271 } 272 273 /// The specified value of a component of a CSS `<transform-origin>`. 274 #[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] 275 pub enum OriginComponent<S> { 276 /// `center` 277 Center, 278 /// `<length-percentage>` 279 Length(LengthPercentage), 280 /// `<side>` 281 Side(S), 282 } 283 284 impl Parse for TransformOrigin { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>285 fn parse<'i, 't>( 286 context: &ParserContext, 287 input: &mut Parser<'i, 't>, 288 ) -> Result<Self, ParseError<'i>> { 289 let parse_depth = |input: &mut Parser| { 290 input 291 .try_parse(|i| Length::parse(context, i)) 292 .unwrap_or(Length::zero()) 293 }; 294 match input.try_parse(|i| OriginComponent::parse(context, i)) { 295 Ok(x_origin @ OriginComponent::Center) => { 296 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) { 297 let depth = parse_depth(input); 298 return Ok(Self::new(x_origin, y_origin, depth)); 299 } 300 let y_origin = OriginComponent::Center; 301 if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) { 302 let x_origin = OriginComponent::Side(x_keyword); 303 let depth = parse_depth(input); 304 return Ok(Self::new(x_origin, y_origin, depth)); 305 } 306 let depth = Length::from_px(0.); 307 return Ok(Self::new(x_origin, y_origin, depth)); 308 }, 309 Ok(x_origin) => { 310 if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) { 311 let depth = parse_depth(input); 312 return Ok(Self::new(x_origin, y_origin, depth)); 313 } 314 let y_origin = OriginComponent::Center; 315 let depth = Length::from_px(0.); 316 return Ok(Self::new(x_origin, y_origin, depth)); 317 }, 318 Err(_) => {}, 319 } 320 let y_keyword = VerticalPositionKeyword::parse(input)?; 321 let y_origin = OriginComponent::Side(y_keyword); 322 if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) { 323 let x_origin = OriginComponent::Side(x_keyword); 324 let depth = parse_depth(input); 325 return Ok(Self::new(x_origin, y_origin, depth)); 326 } 327 if input 328 .try_parse(|i| i.expect_ident_matching("center")) 329 .is_ok() 330 { 331 let x_origin = OriginComponent::Center; 332 let depth = parse_depth(input); 333 return Ok(Self::new(x_origin, y_origin, depth)); 334 } 335 let x_origin = OriginComponent::Center; 336 let depth = Length::from_px(0.); 337 Ok(Self::new(x_origin, y_origin, depth)) 338 } 339 } 340 341 impl<S> ToComputedValue for OriginComponent<S> 342 where 343 S: Side, 344 { 345 type ComputedValue = ComputedLengthPercentage; 346 to_computed_value(&self, context: &Context) -> Self::ComputedValue347 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { 348 match *self { 349 OriginComponent::Center => { 350 ComputedLengthPercentage::new_percent(ComputedPercentage(0.5)) 351 }, 352 OriginComponent::Length(ref length) => length.to_computed_value(context), 353 OriginComponent::Side(ref keyword) => { 354 let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. }); 355 ComputedLengthPercentage::new_percent(p) 356 }, 357 } 358 } 359 from_computed_value(computed: &Self::ComputedValue) -> Self360 fn from_computed_value(computed: &Self::ComputedValue) -> Self { 361 OriginComponent::Length(ToComputedValue::from_computed_value(computed)) 362 } 363 } 364 365 impl<S> OriginComponent<S> { 366 /// `0%` zero() -> Self367 pub fn zero() -> Self { 368 OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage::zero())) 369 } 370 } 371 372 /// A specified CSS `rotate` 373 pub type Rotate = generic::Rotate<Number, Angle>; 374 375 impl Parse for Rotate { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>376 fn parse<'i, 't>( 377 context: &ParserContext, 378 input: &mut Parser<'i, 't>, 379 ) -> Result<Self, ParseError<'i>> { 380 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { 381 return Ok(generic::Rotate::None); 382 } 383 384 // Parse <angle> or [ x | y | z | <number>{3} ] && <angle>. 385 // 386 // The rotate axis and angle could be in any order, so we parse angle twice to cover 387 // two cases. i.e. `<number>{3} <angle>` or `<angle> <number>{3}` 388 let angle = input 389 .try_parse(|i| specified::Angle::parse(context, i)) 390 .ok(); 391 let axis = input 392 .try_parse(|i| { 393 Ok(try_match_ident_ignore_ascii_case! { i, 394 "x" => (Number::new(1.), Number::new(0.), Number::new(0.)), 395 "y" => (Number::new(0.), Number::new(1.), Number::new(0.)), 396 "z" => (Number::new(0.), Number::new(0.), Number::new(1.)), 397 }) 398 }) 399 .or_else(|_: ParseError| -> Result<_, ParseError> { 400 input.try_parse(|i| { 401 Ok(( 402 Number::parse(context, i)?, 403 Number::parse(context, i)?, 404 Number::parse(context, i)?, 405 )) 406 }) 407 }) 408 .ok(); 409 let angle = match angle { 410 Some(a) => a, 411 None => specified::Angle::parse(context, input)?, 412 }; 413 414 Ok(match axis { 415 Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle), 416 None => generic::Rotate::Rotate(angle), 417 }) 418 } 419 } 420 421 /// A specified CSS `translate` 422 pub type Translate = generic::Translate<LengthPercentage, Length>; 423 424 impl Parse for Translate { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>425 fn parse<'i, 't>( 426 context: &ParserContext, 427 input: &mut Parser<'i, 't>, 428 ) -> Result<Self, ParseError<'i>> { 429 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { 430 return Ok(generic::Translate::None); 431 } 432 433 let tx = specified::LengthPercentage::parse(context, input)?; 434 if let Ok(ty) = input.try_parse(|i| specified::LengthPercentage::parse(context, i)) { 435 if let Ok(tz) = input.try_parse(|i| specified::Length::parse(context, i)) { 436 // 'translate: <length-percentage> <length-percentage> <length>' 437 return Ok(generic::Translate::Translate(tx, ty, tz)); 438 } 439 440 // translate: <length-percentage> <length-percentage>' 441 return Ok(generic::Translate::Translate( 442 tx, 443 ty, 444 specified::Length::zero(), 445 )); 446 } 447 448 // 'translate: <length-percentage> ' 449 Ok(generic::Translate::Translate( 450 tx, 451 specified::LengthPercentage::zero(), 452 specified::Length::zero(), 453 )) 454 } 455 } 456 457 /// A specified CSS `scale` 458 pub type Scale = generic::Scale<Number>; 459 460 impl Parse for Scale { 461 /// Scale accepts <number> | <percentage>, so we parse it as NumberOrPercentage, 462 /// and then convert into an Number if it's a Percentage. 463 /// https://github.com/w3c/csswg-drafts/pull/4396 parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>464 fn parse<'i, 't>( 465 context: &ParserContext, 466 input: &mut Parser<'i, 't>, 467 ) -> Result<Self, ParseError<'i>> { 468 if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { 469 return Ok(generic::Scale::None); 470 } 471 472 let sx = NumberOrPercentage::parse(context, input)?.to_number(); 473 if let Ok(sy) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) { 474 let sy = sy.to_number(); 475 if let Ok(sz) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) { 476 // 'scale: <number> <number> <number>' 477 return Ok(generic::Scale::Scale(sx, sy, sz.to_number())); 478 } 479 480 // 'scale: <number> <number>' 481 return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0))); 482 } 483 484 // 'scale: <number>' 485 Ok(generic::Scale::Scale(sx, sx, Number::new(1.0))) 486 } 487 } 488