use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Context, LengthPercentage as ComputedLengthPercentage};
use crate::values::computed::{Percentage as ComputedPercentage, ToComputedValue};
use crate::values::generics::transform as generic;
use crate::values::generics::transform::{Matrix, Matrix3D};
use crate::values::specified::position::{
HorizontalPositionKeyword, Side, VerticalPositionKeyword,
};
use crate::values::specified::{
self, Angle, Integer, Length, LengthPercentage, Number, NumberOrPercentage,
};
use crate::Zero;
use cssparser::Parser;
use style_traits::{ParseError, StyleParseErrorKind};
pub use crate::values::generics::transform::TransformStyle;
pub type TransformOperation =
generic::TransformOperation<Angle, Number, Length, Integer, LengthPercentage>;
pub type Transform = generic::Transform<TransformOperation>;
pub type TransformOrigin = generic::TransformOrigin<
OriginComponent<HorizontalPositionKeyword>,
OriginComponent<VerticalPositionKeyword>,
Length,
>;
#[cfg(feature = "gecko")]
fn all_transform_boxes_are_enabled(_context: &ParserContext) -> bool {
static_prefs::pref!("layout.css.transform-box-content-stroke.enabled")
}
#[cfg(feature = "servo")]
fn all_transform_boxes_are_enabled(_context: &ParserContext) -> bool {
false
}
#[allow(missing_docs)]
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
Deserialize,
MallocSizeOf,
Parse,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum TransformBox {
#[parse(condition = "all_transform_boxes_are_enabled")]
ContentBox,
BorderBox,
FillBox,
#[parse(condition = "all_transform_boxes_are_enabled")]
StrokeBox,
ViewBox,
}
impl TransformOrigin {
#[inline]
pub fn initial_value() -> Self {
Self::new(
OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))),
OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))),
Length::zero(),
)
}
pub fn zero_zero() -> Self {
Self::new(
OriginComponent::Length(LengthPercentage::zero()),
OriginComponent::Length(LengthPercentage::zero()),
Length::zero(),
)
}
}
impl Transform {
fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
use style_traits::{Separator, Space};
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(generic::Transform::none());
}
Ok(generic::Transform(
Space::parse(input, |input| {
let function = input.expect_function()?.clone();
input.parse_nested_block(|input| {
let location = input.current_source_location();
let result = match_ignore_ascii_case! { &function,
"matrix" => {
let a = Number::parse(context, input)?;
input.expect_comma()?;
let b = Number::parse(context, input)?;
input.expect_comma()?;
let c = Number::parse(context, input)?;
input.expect_comma()?;
let d = Number::parse(context, input)?;
input.expect_comma()?;
let e = Number::parse(context, input)?;
input.expect_comma()?;
let f = Number::parse(context, input)?;
Ok(generic::TransformOperation::Matrix(Matrix { a, b, c, d, e, f }))
},
"matrix3d" => {
let m11 = Number::parse(context, input)?;
input.expect_comma()?;
let m12 = Number::parse(context, input)?;
input.expect_comma()?;
let m13 = Number::parse(context, input)?;
input.expect_comma()?;
let m14 = Number::parse(context, input)?;
input.expect_comma()?;
let m21 = Number::parse(context, input)?;
input.expect_comma()?;
let m22 = Number::parse(context, input)?;
input.expect_comma()?;
let m23 = Number::parse(context, input)?;
input.expect_comma()?;
let m24 = Number::parse(context, input)?;
input.expect_comma()?;
let m31 = Number::parse(context, input)?;
input.expect_comma()?;
let m32 = Number::parse(context, input)?;
input.expect_comma()?;
let m33 = Number::parse(context, input)?;
input.expect_comma()?;
let m34 = Number::parse(context, input)?;
input.expect_comma()?;
let m41 = Number::parse(context, input)?;
input.expect_comma()?;
let m42 = Number::parse(context, input)?;
input.expect_comma()?;
let m43 = Number::parse(context, input)?;
input.expect_comma()?;
let m44 = Number::parse(context, input)?;
Ok(generic::TransformOperation::Matrix3D(Matrix3D {
m11, m12, m13, m14,
m21, m22, m23, m24,
m31, m32, m33, m34,
m41, m42, m43, m44,
}))
},
"translate" => {
let sx = specified::LengthPercentage::parse(context, input)?;
if input.try_parse(|input| input.expect_comma()).is_ok() {
let sy = specified::LengthPercentage::parse(context, input)?;
Ok(generic::TransformOperation::Translate(sx, sy))
} else {
Ok(generic::TransformOperation::Translate(sx, Zero::zero()))
}
},
"translatex" => {
let tx = specified::LengthPercentage::parse(context, input)?;
Ok(generic::TransformOperation::TranslateX(tx))
},
"translatey" => {
let ty = specified::LengthPercentage::parse(context, input)?;
Ok(generic::TransformOperation::TranslateY(ty))
},
"translatez" => {
let tz = specified::Length::parse(context, input)?;
Ok(generic::TransformOperation::TranslateZ(tz))
},
"translate3d" => {
let tx = specified::LengthPercentage::parse(context, input)?;
input.expect_comma()?;
let ty = specified::LengthPercentage::parse(context, input)?;
input.expect_comma()?;
let tz = specified::Length::parse(context, input)?;
Ok(generic::TransformOperation::Translate3D(tx, ty, tz))
},
"scale" => {
let sx = NumberOrPercentage::parse(context, input)?.to_number();
if input.try_parse(|input| input.expect_comma()).is_ok() {
let sy = NumberOrPercentage::parse(context, input)?.to_number();
Ok(generic::TransformOperation::Scale(sx, sy))
} else {
Ok(generic::TransformOperation::Scale(sx, sx))
}
},
"scalex" => {
let sx = NumberOrPercentage::parse(context, input)?.to_number();
Ok(generic::TransformOperation::ScaleX(sx))
},
"scaley" => {
let sy = NumberOrPercentage::parse(context, input)?.to_number();
Ok(generic::TransformOperation::ScaleY(sy))
},
"scalez" => {
let sz = NumberOrPercentage::parse(context, input)?.to_number();
Ok(generic::TransformOperation::ScaleZ(sz))
},
"scale3d" => {
let sx = NumberOrPercentage::parse(context, input)?.to_number();
input.expect_comma()?;
let sy = NumberOrPercentage::parse(context, input)?.to_number();
input.expect_comma()?;
let sz = NumberOrPercentage::parse(context, input)?.to_number();
Ok(generic::TransformOperation::Scale3D(sx, sy, sz))
},
"rotate" => {
let theta = specified::Angle::parse_with_unitless(context, input)?;
Ok(generic::TransformOperation::Rotate(theta))
},
"rotatex" => {
let theta = specified::Angle::parse_with_unitless(context, input)?;
Ok(generic::TransformOperation::RotateX(theta))
},
"rotatey" => {
let theta = specified::Angle::parse_with_unitless(context, input)?;
Ok(generic::TransformOperation::RotateY(theta))
},
"rotatez" => {
let theta = specified::Angle::parse_with_unitless(context, input)?;
Ok(generic::TransformOperation::RotateZ(theta))
},
"rotate3d" => {
let ax = Number::parse(context, input)?;
input.expect_comma()?;
let ay = Number::parse(context, input)?;
input.expect_comma()?;
let az = Number::parse(context, input)?;
input.expect_comma()?;
let theta = specified::Angle::parse_with_unitless(context, input)?;
Ok(generic::TransformOperation::Rotate3D(ax, ay, az, theta))
},
"skew" => {
let ax = specified::Angle::parse_with_unitless(context, input)?;
if input.try_parse(|input| input.expect_comma()).is_ok() {
let ay = specified::Angle::parse_with_unitless(context, input)?;
Ok(generic::TransformOperation::Skew(ax, ay))
} else {
Ok(generic::TransformOperation::Skew(ax, Zero::zero()))
}
},
"skewx" => {
let theta = specified::Angle::parse_with_unitless(context, input)?;
Ok(generic::TransformOperation::SkewX(theta))
},
"skewy" => {
let theta = specified::Angle::parse_with_unitless(context, input)?;
Ok(generic::TransformOperation::SkewY(theta))
},
"perspective" => {
let p = match input.try_parse(|input| specified::Length::parse_non_negative(context, input)) {
Ok(p) => generic::PerspectiveFunction::Length(p),
Err(..) => {
input.expect_ident_matching("none")?;
generic::PerspectiveFunction::None
}
};
Ok(generic::TransformOperation::Perspective(p))
},
_ => Err(()),
};
result.map_err(|()| {
location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(
function.clone(),
))
})
})
})?
.into(),
))
}
}
impl Parse for Transform {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Transform::parse_internal(context, input)
}
}
#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub enum OriginComponent<S> {
Center,
Length(LengthPercentage),
Side(S),
}
impl Parse for TransformOrigin {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let parse_depth = |input: &mut Parser| {
input
.try_parse(|i| Length::parse(context, i))
.unwrap_or(Length::zero())
};
match input.try_parse(|i| OriginComponent::parse(context, i)) {
Ok(x_origin @ OriginComponent::Center) => {
if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
let depth = parse_depth(input);
return Ok(Self::new(x_origin, y_origin, depth));
}
let y_origin = OriginComponent::Center;
if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
let x_origin = OriginComponent::Side(x_keyword);
let depth = parse_depth(input);
return Ok(Self::new(x_origin, y_origin, depth));
}
let depth = Length::from_px(0.);
return Ok(Self::new(x_origin, y_origin, depth));
},
Ok(x_origin) => {
if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) {
let depth = parse_depth(input);
return Ok(Self::new(x_origin, y_origin, depth));
}
let y_origin = OriginComponent::Center;
let depth = Length::from_px(0.);
return Ok(Self::new(x_origin, y_origin, depth));
},
Err(_) => {},
}
let y_keyword = VerticalPositionKeyword::parse(input)?;
let y_origin = OriginComponent::Side(y_keyword);
if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) {
let x_origin = OriginComponent::Side(x_keyword);
let depth = parse_depth(input);
return Ok(Self::new(x_origin, y_origin, depth));
}
if input
.try_parse(|i| i.expect_ident_matching("center"))
.is_ok()
{
let x_origin = OriginComponent::Center;
let depth = parse_depth(input);
return Ok(Self::new(x_origin, y_origin, depth));
}
let x_origin = OriginComponent::Center;
let depth = Length::from_px(0.);
Ok(Self::new(x_origin, y_origin, depth))
}
}
impl<S> ToComputedValue for OriginComponent<S>
where
S: Side,
{
type ComputedValue = ComputedLengthPercentage;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self {
OriginComponent::Center => {
ComputedLengthPercentage::new_percent(ComputedPercentage(0.5))
},
OriginComponent::Length(ref length) => length.to_computed_value(context),
OriginComponent::Side(ref keyword) => {
let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
ComputedLengthPercentage::new_percent(p)
},
}
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
OriginComponent::Length(ToComputedValue::from_computed_value(computed))
}
}
impl<S> OriginComponent<S> {
pub fn zero() -> Self {
OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage::zero()))
}
}
pub type Rotate = generic::Rotate<Number, Angle>;
impl Parse for Rotate {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(generic::Rotate::None);
}
let angle = input
.try_parse(|i| specified::Angle::parse(context, i))
.ok();
let axis = input
.try_parse(|i| {
Ok(try_match_ident_ignore_ascii_case! { i,
"x" => (Number::new(1.), Number::new(0.), Number::new(0.)),
"y" => (Number::new(0.), Number::new(1.), Number::new(0.)),
"z" => (Number::new(0.), Number::new(0.), Number::new(1.)),
})
})
.or_else(|_: ParseError| -> Result<_, ParseError> {
input.try_parse(|i| {
Ok((
Number::parse(context, i)?,
Number::parse(context, i)?,
Number::parse(context, i)?,
))
})
})
.ok();
let angle = match angle {
Some(a) => a,
None => specified::Angle::parse(context, input)?,
};
Ok(match axis {
Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle),
None => generic::Rotate::Rotate(angle),
})
}
}
pub type Translate = generic::Translate<LengthPercentage, Length>;
impl Parse for Translate {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(generic::Translate::None);
}
let tx = specified::LengthPercentage::parse(context, input)?;
if let Ok(ty) = input.try_parse(|i| specified::LengthPercentage::parse(context, i)) {
if let Ok(tz) = input.try_parse(|i| specified::Length::parse(context, i)) {
return Ok(generic::Translate::Translate(tx, ty, tz));
}
return Ok(generic::Translate::Translate(
tx,
ty,
specified::Length::zero(),
));
}
Ok(generic::Translate::Translate(
tx,
specified::LengthPercentage::zero(),
specified::Length::zero(),
))
}
}
pub type Scale = generic::Scale<Number>;
impl Parse for Scale {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(generic::Scale::None);
}
let sx = NumberOrPercentage::parse(context, input)?.to_number();
if let Ok(sy) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
let sy = sy.to_number();
if let Ok(sz) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) {
return Ok(generic::Scale::Scale(sx, sy, sz.to_number()));
}
return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0)));
}
Ok(generic::Scale::Scale(sx, sx, Number::new(1.0)))
}
}