#![warn(unused_must_use)]
use bun_alloc::Arena as Bump;
use bun_alloc::ArenaVecExt as _;
use crate::css_properties::{Property, PropertyIdTag};
use crate::css_values::angle::Angle;
use crate::css_values::length::{LengthPercentage, LengthValue as Length};
use crate::css_values::number::CSSNumberFns;
use crate::css_values::percentage::NumberOrPercentage;
use crate::prefixes;
use crate::{
DeclarationList, Parser, PrintErr, Printer, PropertyHandlerContext, Result, Token, VendorPrefix,
};
#[derive(Clone, PartialEq, Default)]
pub struct TransformList {
pub v: Vec<Transform>,
}
impl TransformList {
pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self {
self.clone()
}
}
impl TransformList {
pub(crate) fn parse(input: &mut Parser<'_>) -> Result<Self> {
if input
.try_parse(|i| i.expect_ident_matching(b"none"))
.is_ok()
{
return Ok(Self { v: Vec::new() });
}
input.skip_whitespace();
let mut results = Vec::<Transform>::new();
let first = Transform::parse(input)?;
results.push(first);
loop {
input.skip_whitespace();
if let Ok(item) = input.try_parse(Transform::parse) {
results.push(item);
} else {
return Ok(Self { v: results });
}
}
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
if self.v.is_empty() {
return dest.write_str("none");
}
self.to_css_base(dest)
}
fn to_css_base(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
for item in self.v.iter() {
item.to_css(dest)?;
}
Ok(())
}
}
#[derive(Clone, PartialEq)]
pub enum Transform {
Translate {
x: LengthPercentage,
y: LengthPercentage,
},
TranslateX(LengthPercentage),
TranslateY(LengthPercentage),
TranslateZ(Length),
Translate3d {
x: LengthPercentage,
y: LengthPercentage,
z: Length,
},
Scale {
x: NumberOrPercentage,
y: NumberOrPercentage,
},
ScaleX(NumberOrPercentage),
ScaleY(NumberOrPercentage),
ScaleZ(NumberOrPercentage),
Scale3d {
x: NumberOrPercentage,
y: NumberOrPercentage,
z: NumberOrPercentage,
},
Rotate(Angle),
RotateX(Angle),
RotateY(Angle),
RotateZ(Angle),
Rotate3d {
x: f32,
y: f32,
z: f32,
angle: Angle,
},
Skew { x: Angle, y: Angle },
SkewX(Angle),
SkewY(Angle),
Perspective(Length),
Matrix(Matrix<f32>),
Matrix3d(Matrix3d<f32>),
}
impl Transform {
pub(crate) fn parse(input: &mut Parser) -> Result<Transform> {
let function = input.expect_function_cloned()?;
input.parse_nested_block(|i| -> Result<Transform> {
let location = i.current_source_location();
crate::match_ignore_ascii_case! { function, {
b"matrix" => {
let a = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let b = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let c = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let d = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let e = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let f = CSSNumberFns::parse(i)?;
Ok(Transform::Matrix(Matrix { a, b, c, d, e, f }))
},
b"matrix3d" => {
let m11 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m12 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m13 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m14 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m21 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m22 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m23 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m24 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m31 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m32 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m33 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m34 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m41 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m42 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m43 = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let m44 = CSSNumberFns::parse(i)?;
Ok(Transform::Matrix3d(Matrix3d {
m11, m12, m13, m14,
m21, m22, m23, m24,
m31, m32, m33, m34,
m41, m42, m43, m44,
}))
},
b"translate" => {
let x = LengthPercentage::parse(i)?;
if i.try_parse(|p| p.expect_comma()).is_ok() {
let y = LengthPercentage::parse(i)?;
Ok(Transform::Translate { x, y })
} else {
Ok(Transform::Translate { x, y: LengthPercentage::zero() })
}
},
b"translatex" => {
let x = LengthPercentage::parse(i)?;
Ok(Transform::TranslateX(x))
},
b"translatey" => {
let y = LengthPercentage::parse(i)?;
Ok(Transform::TranslateY(y))
},
b"translatez" => {
let z = Length::parse(i)?;
Ok(Transform::TranslateZ(z))
},
b"translate3d" => {
let x = LengthPercentage::parse(i)?;
i.expect_comma()?;
let y = LengthPercentage::parse(i)?;
i.expect_comma()?;
let z = Length::parse(i)?;
Ok(Transform::Translate3d { x, y, z })
},
b"scale" => {
let x = NumberOrPercentage::parse(i)?;
if i.try_parse(|p| p.expect_comma()).is_ok() {
let y = NumberOrPercentage::parse(i)?;
Ok(Transform::Scale { x, y })
} else {
let y = x.clone();
Ok(Transform::Scale { x, y })
}
},
b"scalex" => {
let x = NumberOrPercentage::parse(i)?;
Ok(Transform::ScaleX(x))
},
b"scaley" => {
let y = NumberOrPercentage::parse(i)?;
Ok(Transform::ScaleY(y))
},
b"scalez" => {
let z = NumberOrPercentage::parse(i)?;
Ok(Transform::ScaleZ(z))
},
b"scale3d" => {
let x = NumberOrPercentage::parse(i)?;
i.expect_comma()?;
let y = NumberOrPercentage::parse(i)?;
i.expect_comma()?;
let z = NumberOrPercentage::parse(i)?;
Ok(Transform::Scale3d { x, y, z })
},
b"rotate" => {
let angle = Angle::parse_with_unitless_zero(i)?;
Ok(Transform::Rotate(angle))
},
b"rotatex" => {
let angle = Angle::parse_with_unitless_zero(i)?;
Ok(Transform::RotateX(angle))
},
b"rotatey" => {
let angle = Angle::parse_with_unitless_zero(i)?;
Ok(Transform::RotateY(angle))
},
b"rotatez" => {
let angle = Angle::parse_with_unitless_zero(i)?;
Ok(Transform::RotateZ(angle))
},
b"rotate3d" => {
let x = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let y = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let z = CSSNumberFns::parse(i)?;
i.expect_comma()?;
let angle = Angle::parse_with_unitless_zero(i)?;
Ok(Transform::Rotate3d { x, y, z, angle })
},
b"skew" => {
let x = Angle::parse_with_unitless_zero(i)?;
if i.try_parse(|p| p.expect_comma()).is_ok() {
let y = Angle::parse_with_unitless_zero(i)?;
Ok(Transform::Skew { x, y })
} else {
Ok(Transform::Skew { x, y: Angle::Deg(0.0) })
}
},
b"skewx" => {
let angle = Angle::parse_with_unitless_zero(i)?;
Ok(Transform::SkewX(angle))
},
b"skewy" => {
let angle = Angle::parse_with_unitless_zero(i)?;
Ok(Transform::SkewY(angle))
},
b"perspective" => {
let len = Length::parse(i)?;
Ok(Transform::Perspective(len))
},
_ => Err(location.new_unexpected_token_error(Token::Ident(function))),
}}
})
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
match self {
Transform::Translate { x, y } => {
if dest.minify && x.is_zero() && !y.is_zero() {
dest.write_str("translateY(")?;
y.to_css(dest)?;
} else {
dest.write_str("translate(")?;
x.to_css(dest)?;
if !y.is_zero() {
dest.delim(b',', false)?;
y.to_css(dest)?;
}
}
dest.write_char(b')')?;
}
Transform::TranslateX(x) => {
dest.write_str(if dest.minify {
"translate("
} else {
"translateX("
})?;
x.to_css(dest)?;
dest.write_char(b')')?;
}
Transform::TranslateY(y) => {
dest.write_str("translateY(")?;
y.to_css(dest)?;
dest.write_char(b')')?;
}
Transform::TranslateZ(z) => {
dest.write_str("translateZ(")?;
z.to_css(dest)?;
dest.write_char(b')')?;
}
Transform::Translate3d { x, y, z } => {
if dest.minify && !x.is_zero() && y.is_zero() && z.is_zero() {
dest.write_str("translate(")?;
x.to_css(dest)?;
} else if dest.minify && x.is_zero() && !y.is_zero() && z.is_zero() {
dest.write_str("translateY(")?;
y.to_css(dest)?;
} else if dest.minify && x.is_zero() && y.is_zero() && !z.is_zero() {
dest.write_str("translateZ(")?;
z.to_css(dest)?;
} else if dest.minify && z.is_zero() {
dest.write_str("translate(")?;
x.to_css(dest)?;
dest.delim(b',', false)?;
y.to_css(dest)?;
} else {
dest.write_str("translate3d(")?;
x.to_css(dest)?;
dest.delim(b',', false)?;
y.to_css(dest)?;
dest.delim(b',', false)?;
z.to_css(dest)?;
}
dest.write_char(b')')?;
}
Transform::Scale { x: sx, y: sy } => {
let x: f32 = sx.into_f32();
let y: f32 = sy.into_f32();
if dest.minify && x == 1.0 && y != 1.0 {
dest.write_str("scaleY(")?;
CSSNumberFns::to_css(y, dest)?;
} else if dest.minify && x != 1.0 && y == 1.0 {
dest.write_str("scaleX(")?;
CSSNumberFns::to_css(x, dest)?;
} else {
dest.write_str("scale(")?;
CSSNumberFns::to_css(x, dest)?;
if y != x {
dest.delim(b',', false)?;
CSSNumberFns::to_css(y, dest)?;
}
}
dest.write_char(b')')?;
}
Transform::ScaleX(x) => {
dest.write_str("scaleX(")?;
CSSNumberFns::to_css(x.into_f32(), dest)?;
dest.write_char(b')')?;
}
Transform::ScaleY(y) => {
dest.write_str("scaleY(")?;
CSSNumberFns::to_css(y.into_f32(), dest)?;
dest.write_char(b')')?;
}
Transform::ScaleZ(z) => {
dest.write_str("scaleZ(")?;
CSSNumberFns::to_css(z.into_f32(), dest)?;
dest.write_char(b')')?;
}
Transform::Scale3d {
x: sx,
y: sy,
z: sz,
} => {
let x: f32 = sx.into_f32();
let y: f32 = sy.into_f32();
let z: f32 = sz.into_f32();
if dest.minify && z == 1.0 && x == y {
dest.write_str("scale(")?;
CSSNumberFns::to_css(x, dest)?;
} else if dest.minify && x != 1.0 && y == 1.0 && z == 1.0 {
dest.write_str("scaleX(")?;
CSSNumberFns::to_css(x, dest)?;
} else if dest.minify && x == 1.0 && y != 1.0 && z == 1.0 {
dest.write_str("scaleY(")?;
CSSNumberFns::to_css(y, dest)?;
} else if dest.minify && x == 1.0 && y == 1.0 && z != 1.0 {
dest.write_str("scaleZ(")?;
CSSNumberFns::to_css(z, dest)?;
} else if dest.minify && z == 1.0 {
dest.write_str("scale(")?;
CSSNumberFns::to_css(x, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(y, dest)?;
} else {
dest.write_str("scale3d(")?;
CSSNumberFns::to_css(x, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(y, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(z, dest)?;
}
dest.write_char(b')')?;
}
Transform::Rotate(angle) => {
dest.write_str("rotate(")?;
angle.to_css_with_unitless_zero(dest)?;
dest.write_char(b')')?;
}
Transform::RotateX(angle) => {
dest.write_str("rotateX(")?;
angle.to_css_with_unitless_zero(dest)?;
dest.write_char(b')')?;
}
Transform::RotateY(angle) => {
dest.write_str("rotateY(")?;
angle.to_css_with_unitless_zero(dest)?;
dest.write_char(b')')?;
}
Transform::RotateZ(angle) => {
dest.write_str(if dest.minify { "rotate(" } else { "rotateZ(" })?;
angle.to_css_with_unitless_zero(dest)?;
dest.write_char(b')')?;
}
Transform::Rotate3d { x, y, z, angle } => {
if dest.minify && *x == 1.0 && *y == 0.0 && *z == 0.0 {
dest.write_str("rotateX(")?;
angle.to_css_with_unitless_zero(dest)?;
} else if dest.minify && *x == 0.0 && *y == 1.0 && *z == 0.0 {
dest.write_str("rotateY(")?;
angle.to_css_with_unitless_zero(dest)?;
} else if dest.minify && *x == 0.0 && *y == 0.0 && *z == 1.0 {
dest.write_str("rotate(")?;
angle.to_css_with_unitless_zero(dest)?;
} else {
dest.write_str("rotate3d(")?;
CSSNumberFns::to_css(*x, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(*y, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(*z, dest)?;
dest.delim(b',', false)?;
angle.to_css_with_unitless_zero(dest)?;
}
dest.write_char(b')')?;
}
Transform::Skew { x, y } => {
if dest.minify && x.is_zero() && !y.is_zero() {
dest.write_str("skewY(")?;
y.to_css_with_unitless_zero(dest)?;
} else {
dest.write_str("skew(")?;
x.to_css(dest)?;
if !y.is_zero() {
dest.delim(b',', false)?;
y.to_css_with_unitless_zero(dest)?;
}
}
dest.write_char(b')')?;
}
Transform::SkewX(angle) => {
dest.write_str(if dest.minify { "skew(" } else { "skewX(" })?;
angle.to_css_with_unitless_zero(dest)?;
dest.write_char(b')')?;
}
Transform::SkewY(angle) => {
dest.write_str("skewY(")?;
angle.to_css_with_unitless_zero(dest)?;
dest.write_char(b')')?;
}
Transform::Perspective(len) => {
dest.write_str("perspective(")?;
len.to_css(dest)?;
dest.write_char(b')')?;
}
Transform::Matrix(m) => {
dest.write_str("matrix(")?;
CSSNumberFns::to_css(m.a, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.b, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.c, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.d, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.e, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.f, dest)?;
dest.write_char(b')')?;
}
Transform::Matrix3d(m) => {
dest.write_str("matrix3d(")?;
CSSNumberFns::to_css(m.m11, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m12, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m13, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m14, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m21, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m22, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m23, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m24, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m31, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m32, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m33, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m34, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m41, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m42, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m43, dest)?;
dest.delim(b',', false)?;
CSSNumberFns::to_css(m.m44, dest)?;
dest.write_char(b')')?;
}
}
Ok(())
}
pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self {
self.clone()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Matrix<T> {
pub a: T,
pub b: T,
pub c: T,
pub d: T,
pub e: T,
pub f: T,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Matrix3d<T> {
pub m11: T,
pub m12: T,
pub m13: T,
pub m14: T,
pub m21: T,
pub m22: T,
pub m23: T,
pub m24: T,
pub m31: T,
pub m32: T,
pub m33: T,
pub m34: T,
pub m41: T,
pub m42: T,
pub m43: T,
pub m44: T,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)]
pub enum TransformStyle {
#[css("flat")]
Flat,
#[css("preserve-3d")]
Preserve3d,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)]
pub enum TransformBox {
#[css("content-box")]
ContentBox,
#[css("border-box")]
BorderBox,
#[css("fill-box")]
FillBox,
#[css("stroke-box")]
StrokeBox,
#[css("view-box")]
ViewBox,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)]
pub enum BackfaceVisibility {
#[css("visible")]
Visible,
#[css("hidden")]
Hidden,
}
#[derive(Clone, PartialEq, crate::Parse, crate::ToCss)]
pub enum Perspective {
None,
Length(Length),
}
impl Perspective {
pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self {
self.clone()
}
}
#[derive(Clone, PartialEq)]
pub enum Translate {
None,
Xyz {
x: LengthPercentage,
y: LengthPercentage,
z: Length,
},
}
impl Translate {
pub(crate) fn parse(input: &mut Parser) -> Result<Self> {
if input
.try_parse(|i| i.expect_ident_matching(b"none"))
.is_ok()
{
return Ok(Translate::None);
}
let x = LengthPercentage::parse(input)?;
let y = input.try_parse(LengthPercentage::parse);
let z = if y.is_ok() {
input.try_parse(Length::parse).ok()
} else {
None
};
Ok(Translate::Xyz {
x,
y: y.unwrap_or_else(|_| LengthPercentage::zero()),
z: z.unwrap_or_else(Length::zero),
})
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
match self {
Translate::None => dest.write_str("none")?,
Translate::Xyz { x, y, z } => {
x.to_css(dest)?;
if !y.is_zero() || !z.is_zero() {
dest.write_char(b' ')?;
y.to_css(dest)?;
if !z.is_zero() {
dest.write_char(b' ')?;
z.to_css(dest)?;
}
}
}
}
Ok(())
}
}
impl Translate {
pub(crate) fn to_transform(&self, _bump: &Bump) -> Transform {
match self {
Translate::None => Transform::Translate3d {
x: LengthPercentage::zero(),
y: LengthPercentage::zero(),
z: Length::zero(),
},
Translate::Xyz { x, y, z } => Transform::Translate3d {
x: x.clone(),
y: y.clone(),
z: *z,
},
}
}
pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self {
self.clone()
}
}
#[derive(Copy, Clone, PartialEq)]
pub struct Rotate {
pub x: f32,
pub y: f32,
pub z: f32,
pub angle: Angle,
}
impl Rotate {
pub(crate) fn parse(input: &mut Parser) -> Result<Self> {
if input
.try_parse(|i| i.expect_ident_matching(b"none"))
.is_ok()
{
return Ok(Rotate {
x: 0.0,
y: 0.0,
z: 1.0,
angle: Angle::Deg(0.0),
});
}
let angle = input.try_parse(Angle::parse);
struct Xyz {
x: f32,
y: f32,
z: f32,
}
let xyz = match input.try_parse(|i| -> Result<Xyz> {
let location = i.current_source_location();
let ident = i.expect_ident_cloned()?;
crate::match_ignore_ascii_case! { ident, {
b"x" => Ok(Xyz { x: 1.0, y: 0.0, z: 0.0 }),
b"y" => Ok(Xyz { x: 0.0, y: 1.0, z: 0.0 }),
b"z" => Ok(Xyz { x: 0.0, y: 0.0, z: 1.0 }),
_ => Err(location.new_unexpected_token_error(Token::Ident(ident))),
}}
}) {
Ok(v) => v,
Err(_) => input
.try_parse(|i| -> Result<Xyz> {
let x = CSSNumberFns::parse(i)?;
let y = CSSNumberFns::parse(i)?;
let z = CSSNumberFns::parse(i)?;
Ok(Xyz { x, y, z })
})
.unwrap_or(Xyz {
x: 0.0,
y: 0.0,
z: 1.0,
}),
};
let final_angle = match angle {
Ok(v) => v,
Err(_) => Angle::parse(input)?,
};
Ok(Rotate {
x: xyz.x,
y: xyz.y,
z: xyz.z,
angle: final_angle,
})
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
if self.x == 0.0 && self.y == 0.0 && self.z == 1.0 && self.angle.is_zero() {
dest.write_str("none")?;
return Ok(());
}
if self.x == 1.0 && self.y == 0.0 && self.z == 0.0 {
dest.write_str("x ")?;
} else if self.x == 0.0 && self.y == 1.0 && self.z == 0.0 {
dest.write_str("y ")?;
} else if !(self.x == 0.0 && self.y == 0.0 && self.z == 1.0) {
CSSNumberFns::to_css(self.x, dest)?;
dest.write_char(b' ')?;
CSSNumberFns::to_css(self.y, dest)?;
dest.write_char(b' ')?;
CSSNumberFns::to_css(self.z, dest)?;
dest.write_char(b' ')?;
}
self.angle.to_css(dest)
}
}
impl Rotate {
pub(crate) fn to_transform(&self, _bump: &Bump) -> Transform {
Transform::Rotate3d {
x: self.x,
y: self.y,
z: self.z,
angle: self.angle,
}
}
pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self {
*self
}
}
#[derive(Clone, PartialEq)]
pub enum Scale {
None,
Xyz {
x: NumberOrPercentage,
y: NumberOrPercentage,
z: NumberOrPercentage,
},
}
impl Scale {
pub(crate) fn parse(input: &mut Parser) -> Result<Self> {
if input
.try_parse(|i| i.expect_ident_matching(b"none"))
.is_ok()
{
return Ok(Scale::None);
}
let x = NumberOrPercentage::parse(input)?;
let y = input.try_parse(NumberOrPercentage::parse);
let z = if y.is_ok() {
input.try_parse(NumberOrPercentage::parse).ok()
} else {
None
};
Ok(Scale::Xyz {
x: x.clone(),
y: if let Ok(val) = y { val } else { x },
z: if let Some(val) = z {
val
} else {
NumberOrPercentage::Number(1.0)
},
})
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr> {
match self {
Scale::None => dest.write_str("none")?,
Scale::Xyz { x, y, z } => {
x.to_css(dest)?;
let z_val = z.into_f32();
if y != x || z_val != 1.0 {
dest.write_char(b' ')?;
y.to_css(dest)?;
if z_val != 1.0 {
dest.write_char(b' ')?;
z.to_css(dest)?;
}
}
}
}
Ok(())
}
}
impl Scale {
pub(crate) fn to_transform(&self, _bump: &Bump) -> Transform {
match self {
Scale::None => Transform::Scale3d {
x: NumberOrPercentage::Number(1.0),
y: NumberOrPercentage::Number(1.0),
z: NumberOrPercentage::Number(1.0),
},
Scale::Xyz { x, y, z } => Transform::Scale3d {
x: x.clone(),
y: y.clone(),
z: z.clone(),
},
}
}
pub(crate) fn deep_clone(&self, _bump: &Bump) -> Self {
self.clone()
}
}
crate::css_eql_partialeq!(
TransformList,
Transform,
Perspective,
Translate,
Rotate,
Scale
);
#[derive(Default)]
pub struct TransformHandler {
pub transform: Option<(TransformList, VendorPrefix)>,
pub translate: Option<Translate>,
pub rotate: Option<Rotate>,
pub scale: Option<Scale>,
pub has_any: bool,
}
impl TransformHandler {
pub(crate) fn handle_property(
&mut self,
property: &Property,
dest: &mut DeclarationList,
context: &mut PropertyHandlerContext,
) -> bool {
let bump = dest.bump();
macro_rules! individual_property {
($field:ident, $val:expr) => {{
if let Some(transform) = &mut self.transform {
transform.0.v.push($val.to_transform(bump));
} else {
self.$field = Some($val.deep_clone(bump));
self.has_any = true;
}
}};
}
match property {
Property::Transform(val) => {
let transform_val = &val.0;
let vp = val.1;
let needs_flush = if let Some(current) = &self.transform {
current.0 != *transform_val && !current.1.contains(vp)
} else {
false
};
if needs_flush {
self.flush(dest, context);
}
if let Some(transform) = &mut self.transform {
*transform = (transform_val.deep_clone(bump), transform.1 | vp);
} else {
self.transform = Some((transform_val.deep_clone(bump), vp));
self.has_any = true;
}
self.translate = None;
self.rotate = None;
self.scale = None;
}
Property::Translate(val) => individual_property!(translate, val),
Property::Rotate(val) => individual_property!(rotate, val),
Property::Scale(val) => individual_property!(scale, val),
Property::Unparsed(unparsed) => {
if matches!(
unparsed.property_id.tag(),
PropertyIdTag::Transform
| PropertyIdTag::Translate
| PropertyIdTag::Rotate
| PropertyIdTag::Scale
) {
self.flush(dest, context);
let prop = if unparsed.property_id.tag() == PropertyIdTag::Transform {
Property::Unparsed(unparsed.get_prefixed(
bump,
&context.targets,
prefixes::Feature::Transform,
))
} else {
Property::Unparsed(unparsed.deep_clone(bump))
};
dest.push(prop);
} else {
return false;
}
}
_ => return false,
}
true
}
pub(crate) fn finalize(
&mut self,
dest: &mut DeclarationList,
context: &mut PropertyHandlerContext,
) {
self.flush(dest, context);
}
fn flush(&mut self, dest: &mut DeclarationList, context: &mut PropertyHandlerContext) {
if !self.has_any {
return;
}
self.has_any = false;
let transform = self.transform.take();
let translate = self.translate.take();
let rotate = self.rotate.take();
let scale = self.scale.take();
if let Some(t) = transform {
let prefix = context.targets.prefixes(t.1, prefixes::Feature::Transform);
dest.push(Property::Transform((t.0, prefix)));
}
if let Some(t) = translate {
dest.push(Property::Translate(t));
}
if let Some(r) = rotate {
dest.push(Property::Rotate(r));
}
if let Some(s) = scale {
dest.push(Property::Scale(s));
}
}
}