Skip to main content

azul_css/props/style/
transform.rs

1//! CSS properties for 2D and 3D transformations.
2
3use alloc::{
4    string::{String, ToString},
5    vec::Vec,
6};
7use core::fmt;
8use std::num::ParseFloatError;
9
10#[cfg(feature = "parser")]
11use crate::props::basic::{
12    length::parse_float_value,
13    parse::{parse_parentheses, ParenthesisParseError, ParenthesisParseErrorOwned},
14};
15use crate::{
16    format_rust_code::GetHash,
17    props::{
18        basic::{
19            angle::{
20                parse_angle_value, AngleValue, CssAngleValueParseError,
21                CssAngleValueParseErrorOwned,
22            },
23            length::{PercentageParseError, PercentageValue},
24            pixel::{
25                parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
26                PixelValue,
27            },
28            FloatValue,
29        },
30        formatter::PrintAsCssValue,
31    },
32};
33
34// -- Data Structures --
35
36/// Represents a `perspective-origin` attribute
37#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
38#[repr(C)]
39pub struct StylePerspectiveOrigin {
40    pub x: PixelValue,
41    pub y: PixelValue,
42}
43
44impl StylePerspectiveOrigin {
45    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
46        Self {
47            x: self.x.interpolate(&other.x, t),
48            y: self.y.interpolate(&other.y, t),
49        }
50    }
51}
52
53impl PrintAsCssValue for StylePerspectiveOrigin {
54    fn print_as_css_value(&self) -> String {
55        format!("{} {}", self.x, self.y)
56    }
57}
58
59/// Represents a `transform-origin` attribute
60#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(C)]
62pub struct StyleTransformOrigin {
63    pub x: PixelValue,
64    pub y: PixelValue,
65}
66
67impl Default for StyleTransformOrigin {
68    fn default() -> Self {
69        Self {
70            x: PixelValue::const_percent(50),
71            y: PixelValue::const_percent(50),
72        }
73    }
74}
75
76impl StyleTransformOrigin {
77    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
78        Self {
79            x: self.x.interpolate(&other.x, t),
80            y: self.y.interpolate(&other.y, t),
81        }
82    }
83}
84
85impl PrintAsCssValue for StyleTransformOrigin {
86    fn print_as_css_value(&self) -> String {
87        format!("{} {}", self.x, self.y)
88    }
89}
90
91// Formatting to Rust code
92impl crate::format_rust_code::FormatAsRustCode for StylePerspectiveOrigin {
93    fn format_as_rust_code(&self, _tabs: usize) -> String {
94        format!(
95            "StylePerspectiveOrigin {{ x: {}, y: {} }}",
96            crate::format_rust_code::format_pixel_value(&self.x),
97            crate::format_rust_code::format_pixel_value(&self.y)
98        )
99    }
100}
101
102// Formatting to Rust code for StyleTransformOrigin
103impl crate::format_rust_code::FormatAsRustCode for StyleTransformOrigin {
104    fn format_as_rust_code(&self, _tabs: usize) -> String {
105        format!(
106            "StyleTransformOrigin {{ x: {}, y: {} }}",
107            crate::format_rust_code::format_pixel_value(&self.x),
108            crate::format_rust_code::format_pixel_value(&self.y)
109        )
110    }
111}
112
113/// Represents a `backface-visibility` attribute
114#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
115#[repr(C)]
116pub enum StyleBackfaceVisibility {
117    #[default]
118    Visible,
119    Hidden,
120}
121
122impl PrintAsCssValue for StyleBackfaceVisibility {
123    fn print_as_css_value(&self) -> String {
124        String::from(match self {
125            StyleBackfaceVisibility::Hidden => "hidden",
126            StyleBackfaceVisibility::Visible => "visible",
127        })
128    }
129}
130
131/// Represents one component of a `transform` attribute
132#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
133#[repr(C, u8)]
134pub enum StyleTransform {
135    Matrix(StyleTransformMatrix2D),
136    Matrix3D(StyleTransformMatrix3D),
137    Translate(StyleTransformTranslate2D),
138    Translate3D(StyleTransformTranslate3D),
139    TranslateX(PixelValue),
140    TranslateY(PixelValue),
141    TranslateZ(PixelValue),
142    Rotate(AngleValue),
143    Rotate3D(StyleTransformRotate3D),
144    RotateX(AngleValue),
145    RotateY(AngleValue),
146    RotateZ(AngleValue),
147    Scale(StyleTransformScale2D),
148    Scale3D(StyleTransformScale3D),
149    ScaleX(PercentageValue),
150    ScaleY(PercentageValue),
151    ScaleZ(PercentageValue),
152    Skew(StyleTransformSkew2D),
153    SkewX(AngleValue),
154    SkewY(AngleValue),
155    Perspective(PixelValue),
156}
157
158impl_option!(
159    StyleTransform,
160    OptionStyleTransform,
161    [Debug, Copy, Clone, PartialEq, PartialOrd]
162);
163
164impl_vec!(StyleTransform, StyleTransformVec, StyleTransformVecDestructor, StyleTransformVecDestructorType, StyleTransformVecSlice, OptionStyleTransform);
165impl_vec_debug!(StyleTransform, StyleTransformVec);
166impl_vec_partialord!(StyleTransform, StyleTransformVec);
167impl_vec_ord!(StyleTransform, StyleTransformVec);
168impl_vec_clone!(
169    StyleTransform,
170    StyleTransformVec,
171    StyleTransformVecDestructor
172);
173impl_vec_partialeq!(StyleTransform, StyleTransformVec);
174impl_vec_eq!(StyleTransform, StyleTransformVec);
175impl_vec_hash!(StyleTransform, StyleTransformVec);
176
177impl PrintAsCssValue for StyleTransformVec {
178    fn print_as_css_value(&self) -> String {
179        self.as_ref()
180            .iter()
181            .map(|f| f.print_as_css_value())
182            .collect::<Vec<_>>()
183            .join(" ")
184    }
185}
186
187// Formatting to Rust code for StyleTransformVec
188impl crate::format_rust_code::FormatAsRustCode for StyleTransformVec {
189    fn format_as_rust_code(&self, _tabs: usize) -> String {
190        format!(
191            "StyleTransformVec::from_const_slice(STYLE_TRANSFORM_{}_ITEMS)",
192            self.get_hash()
193        )
194    }
195}
196
197impl PrintAsCssValue for StyleTransform {
198    fn print_as_css_value(&self) -> String {
199        match self {
200            StyleTransform::Matrix(m) => format!(
201                "matrix({}, {}, {}, {}, {}, {})",
202                m.a, m.b, m.c, m.d, m.tx, m.ty
203            ),
204            StyleTransform::Matrix3D(m) => format!(
205                "matrix3d({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
206                m.m11,
207                m.m12,
208                m.m13,
209                m.m14,
210                m.m21,
211                m.m22,
212                m.m23,
213                m.m24,
214                m.m31,
215                m.m32,
216                m.m33,
217                m.m34,
218                m.m41,
219                m.m42,
220                m.m43,
221                m.m44
222            ),
223            StyleTransform::Translate(t) => format!("translate({}, {})", t.x, t.y),
224            StyleTransform::Translate3D(t) => format!("translate3d({}, {}, {})", t.x, t.y, t.z),
225            StyleTransform::TranslateX(x) => format!("translateX({})", x),
226            StyleTransform::TranslateY(y) => format!("translateY({})", y),
227            StyleTransform::TranslateZ(z) => format!("translateZ({})", z),
228            StyleTransform::Rotate(r) => format!("rotate({})", r),
229            StyleTransform::Rotate3D(r) => {
230                format!("rotate3d({}, {}, {}, {})", r.x, r.y, r.z, r.angle)
231            }
232            StyleTransform::RotateX(x) => format!("rotateX({})", x),
233            StyleTransform::RotateY(y) => format!("rotateY({})", y),
234            StyleTransform::RotateZ(z) => format!("rotateZ({})", z),
235            StyleTransform::Scale(s) => format!("scale({}, {})", s.x, s.y),
236            StyleTransform::Scale3D(s) => format!("scale3d({}, {}, {})", s.x, s.y, s.z),
237            StyleTransform::ScaleX(x) => format!("scaleX({})", x),
238            StyleTransform::ScaleY(y) => format!("scaleY({})", y),
239            StyleTransform::ScaleZ(z) => format!("scaleZ({})", z),
240            StyleTransform::Skew(sk) => format!("skew({}, {})", sk.x, sk.y),
241            StyleTransform::SkewX(x) => format!("skewX({})", x),
242            StyleTransform::SkewY(y) => format!("skewY({})", y),
243            StyleTransform::Perspective(dist) => format!("perspective({})", dist),
244        }
245    }
246}
247
248#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
249#[repr(C)]
250pub struct StyleTransformMatrix2D {
251    pub a: FloatValue,
252    pub b: FloatValue,
253    pub c: FloatValue,
254    pub d: FloatValue,
255    pub tx: FloatValue,
256    pub ty: FloatValue,
257}
258
259impl Default for StyleTransformMatrix2D {
260    fn default() -> Self {
261        Self {
262            a: FloatValue::const_new(1),
263            b: FloatValue::const_new(0),
264            c: FloatValue::const_new(0),
265            d: FloatValue::const_new(1),
266            tx: FloatValue::const_new(0),
267            ty: FloatValue::const_new(0),
268        }
269    }
270}
271
272#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
273#[repr(C)]
274pub struct StyleTransformMatrix3D {
275    pub m11: FloatValue,
276    pub m12: FloatValue,
277    pub m13: FloatValue,
278    pub m14: FloatValue,
279    pub m21: FloatValue,
280    pub m22: FloatValue,
281    pub m23: FloatValue,
282    pub m24: FloatValue,
283    pub m31: FloatValue,
284    pub m32: FloatValue,
285    pub m33: FloatValue,
286    pub m34: FloatValue,
287    pub m41: FloatValue,
288    pub m42: FloatValue,
289    pub m43: FloatValue,
290    pub m44: FloatValue,
291}
292
293impl Default for StyleTransformMatrix3D {
294    fn default() -> Self {
295        Self {
296            m11: FloatValue::const_new(1),
297            m12: FloatValue::const_new(0),
298            m13: FloatValue::const_new(0),
299            m14: FloatValue::const_new(0),
300            m21: FloatValue::const_new(0),
301            m22: FloatValue::const_new(1),
302            m23: FloatValue::const_new(0),
303            m24: FloatValue::const_new(0),
304            m31: FloatValue::const_new(0),
305            m32: FloatValue::const_new(0),
306            m33: FloatValue::const_new(1),
307            m34: FloatValue::const_new(0),
308            m41: FloatValue::const_new(0),
309            m42: FloatValue::const_new(0),
310            m43: FloatValue::const_new(0),
311            m44: FloatValue::const_new(1),
312        }
313    }
314}
315
316#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
317#[repr(C)]
318pub struct StyleTransformTranslate2D {
319    pub x: PixelValue,
320    pub y: PixelValue,
321}
322
323#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
324#[repr(C)]
325pub struct StyleTransformTranslate3D {
326    pub x: PixelValue,
327    pub y: PixelValue,
328    pub z: PixelValue,
329}
330
331#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
332#[repr(C)]
333pub struct StyleTransformRotate3D {
334    pub x: FloatValue,
335    pub y: FloatValue,
336    pub z: FloatValue,
337    pub angle: AngleValue,
338}
339
340#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
341#[repr(C)]
342pub struct StyleTransformScale2D {
343    pub x: FloatValue,
344    pub y: FloatValue,
345}
346
347#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
348#[repr(C)]
349pub struct StyleTransformScale3D {
350    pub x: FloatValue,
351    pub y: FloatValue,
352    pub z: FloatValue,
353}
354
355#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
356#[repr(C)]
357pub struct StyleTransformSkew2D {
358    pub x: AngleValue,
359    pub y: AngleValue,
360}
361
362// -- Errors --
363
364#[derive(Clone, PartialEq)]
365pub enum CssStyleTransformParseError<'a> {
366    InvalidTransform(&'a str),
367    InvalidParenthesis(ParenthesisParseError<'a>),
368    WrongNumberOfComponents {
369        expected: usize,
370        got: usize,
371        input: &'a str,
372    },
373    NumberParseError(core::num::ParseFloatError),
374    PixelValueParseError(CssPixelValueParseError<'a>),
375    AngleValueParseError(CssAngleValueParseError<'a>),
376    PercentageValueParseError(PercentageParseError),
377}
378
379impl_debug_as_display!(CssStyleTransformParseError<'a>);
380impl_display! { CssStyleTransformParseError<'a>, {
381    InvalidTransform(e) => format!("Invalid transform property: \"{}\"", e),
382    InvalidParenthesis(e) => format!("Invalid transform property - parenthesis error: {}", e),
383    WrongNumberOfComponents { expected, got, input } => format!("Invalid number of components: expected {}, got {}: \"{}\"", expected, got, input),
384    NumberParseError(e) => format!("Could not parse number: {}", e),
385    PixelValueParseError(e) => format!("Invalid pixel value: {}", e),
386    AngleValueParseError(e) => format!("Invalid angle value: {}", e),
387    PercentageValueParseError(e) => format!("Error parsing percentage: {}", e),
388}}
389
390impl_from! { ParenthesisParseError<'a>, CssStyleTransformParseError::InvalidParenthesis }
391impl_from! { CssPixelValueParseError<'a>, CssStyleTransformParseError::PixelValueParseError }
392impl_from! { CssAngleValueParseError<'a>, CssStyleTransformParseError::AngleValueParseError }
393impl_from! { ParseFloatError, CssStyleTransformParseError<'a>::NumberParseError }
394
395impl<'a> From<PercentageParseError> for CssStyleTransformParseError<'a> {
396    fn from(p: PercentageParseError) -> Self {
397        Self::PercentageValueParseError(p)
398    }
399}
400
401#[derive(Debug, Clone, PartialEq)]
402pub enum CssStyleTransformParseErrorOwned {
403    InvalidTransform(String),
404    InvalidParenthesis(ParenthesisParseErrorOwned),
405    WrongNumberOfComponents {
406        expected: usize,
407        got: usize,
408        input: String,
409    },
410    NumberParseError(core::num::ParseFloatError),
411    PixelValueParseError(CssPixelValueParseErrorOwned),
412    AngleValueParseError(CssAngleValueParseErrorOwned),
413    PercentageValueParseError(PercentageParseError),
414}
415
416impl<'a> CssStyleTransformParseError<'a> {
417    pub fn to_contained(&self) -> CssStyleTransformParseErrorOwned {
418        match self {
419            Self::InvalidTransform(s) => {
420                CssStyleTransformParseErrorOwned::InvalidTransform(s.to_string())
421            }
422            Self::InvalidParenthesis(e) => {
423                CssStyleTransformParseErrorOwned::InvalidParenthesis(e.to_contained())
424            }
425            Self::WrongNumberOfComponents {
426                expected,
427                got,
428                input,
429            } => CssStyleTransformParseErrorOwned::WrongNumberOfComponents {
430                expected: *expected,
431                got: *got,
432                input: input.to_string(),
433            },
434            Self::NumberParseError(e) => {
435                CssStyleTransformParseErrorOwned::NumberParseError(e.clone())
436            }
437            Self::PixelValueParseError(e) => {
438                CssStyleTransformParseErrorOwned::PixelValueParseError(e.to_contained())
439            }
440            Self::AngleValueParseError(e) => {
441                CssStyleTransformParseErrorOwned::AngleValueParseError(e.to_contained())
442            }
443            Self::PercentageValueParseError(e) => {
444                CssStyleTransformParseErrorOwned::PercentageValueParseError(e.clone())
445            }
446        }
447    }
448}
449
450impl CssStyleTransformParseErrorOwned {
451    pub fn to_shared<'a>(&'a self) -> CssStyleTransformParseError<'a> {
452        match self {
453            Self::InvalidTransform(s) => CssStyleTransformParseError::InvalidTransform(s),
454            Self::InvalidParenthesis(e) => {
455                CssStyleTransformParseError::InvalidParenthesis(e.to_shared())
456            }
457            Self::WrongNumberOfComponents {
458                expected,
459                got,
460                input,
461            } => CssStyleTransformParseError::WrongNumberOfComponents {
462                expected: *expected,
463                got: *got,
464                input,
465            },
466            Self::NumberParseError(e) => CssStyleTransformParseError::NumberParseError(e.clone()),
467            Self::PixelValueParseError(e) => {
468                CssStyleTransformParseError::PixelValueParseError(e.to_shared())
469            }
470            Self::AngleValueParseError(e) => {
471                CssStyleTransformParseError::AngleValueParseError(e.to_shared())
472            }
473            Self::PercentageValueParseError(e) => {
474                CssStyleTransformParseError::PercentageValueParseError(e.clone())
475            }
476        }
477    }
478}
479
480#[derive(Clone, PartialEq)]
481pub enum CssStyleTransformOriginParseError<'a> {
482    WrongNumberOfComponents {
483        expected: usize,
484        got: usize,
485        input: &'a str,
486    },
487    PixelValueParseError(CssPixelValueParseError<'a>),
488}
489
490impl_debug_as_display!(CssStyleTransformOriginParseError<'a>);
491impl_display! { CssStyleTransformOriginParseError<'a>, {
492    WrongNumberOfComponents { expected, got, input } => format!("Invalid number of components: expected {}, got {}: \"{}\"", expected, got, input),
493    PixelValueParseError(e) => format!("Invalid pixel value: {}", e),
494}}
495impl_from! { CssPixelValueParseError<'a>, CssStyleTransformOriginParseError::PixelValueParseError }
496
497#[derive(Debug, Clone, PartialEq)]
498pub enum CssStyleTransformOriginParseErrorOwned {
499    WrongNumberOfComponents {
500        expected: usize,
501        got: usize,
502        input: String,
503    },
504    PixelValueParseError(CssPixelValueParseErrorOwned),
505}
506
507impl<'a> CssStyleTransformOriginParseError<'a> {
508    pub fn to_contained(&self) -> CssStyleTransformOriginParseErrorOwned {
509        match self {
510            Self::WrongNumberOfComponents {
511                expected,
512                got,
513                input,
514            } => CssStyleTransformOriginParseErrorOwned::WrongNumberOfComponents {
515                expected: *expected,
516                got: *got,
517                input: input.to_string(),
518            },
519            Self::PixelValueParseError(e) => {
520                CssStyleTransformOriginParseErrorOwned::PixelValueParseError(e.to_contained())
521            }
522        }
523    }
524}
525
526impl CssStyleTransformOriginParseErrorOwned {
527    pub fn to_shared<'a>(&'a self) -> CssStyleTransformOriginParseError<'a> {
528        match self {
529            Self::WrongNumberOfComponents {
530                expected,
531                got,
532                input,
533            } => CssStyleTransformOriginParseError::WrongNumberOfComponents {
534                expected: *expected,
535                got: *got,
536                input,
537            },
538            Self::PixelValueParseError(e) => {
539                CssStyleTransformOriginParseError::PixelValueParseError(e.to_shared())
540            }
541        }
542    }
543}
544
545#[derive(Clone, PartialEq)]
546pub enum CssStylePerspectiveOriginParseError<'a> {
547    WrongNumberOfComponents {
548        expected: usize,
549        got: usize,
550        input: &'a str,
551    },
552    PixelValueParseError(CssPixelValueParseError<'a>),
553}
554
555impl_debug_as_display!(CssStylePerspectiveOriginParseError<'a>);
556impl_display! { CssStylePerspectiveOriginParseError<'a>, {
557    WrongNumberOfComponents { expected, got, input } => format!("Invalid number of components: expected {}, got {}: \"{}\"", expected, got, input),
558    PixelValueParseError(e) => format!("Invalid pixel value: {}", e),
559}}
560impl_from! { CssPixelValueParseError<'a>, CssStylePerspectiveOriginParseError::PixelValueParseError }
561
562#[derive(Debug, Clone, PartialEq)]
563pub enum CssStylePerspectiveOriginParseErrorOwned {
564    WrongNumberOfComponents {
565        expected: usize,
566        got: usize,
567        input: String,
568    },
569    PixelValueParseError(CssPixelValueParseErrorOwned),
570}
571
572impl<'a> CssStylePerspectiveOriginParseError<'a> {
573    pub fn to_contained(&self) -> CssStylePerspectiveOriginParseErrorOwned {
574        match self {
575            Self::WrongNumberOfComponents {
576                expected,
577                got,
578                input,
579            } => CssStylePerspectiveOriginParseErrorOwned::WrongNumberOfComponents {
580                expected: *expected,
581                got: *got,
582                input: input.to_string(),
583            },
584            Self::PixelValueParseError(e) => {
585                CssStylePerspectiveOriginParseErrorOwned::PixelValueParseError(e.to_contained())
586            }
587        }
588    }
589}
590
591impl CssStylePerspectiveOriginParseErrorOwned {
592    pub fn to_shared<'a>(&'a self) -> CssStylePerspectiveOriginParseError<'a> {
593        match self {
594            Self::WrongNumberOfComponents {
595                expected,
596                got,
597                input,
598            } => CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
599                expected: *expected,
600                got: *got,
601                input,
602            },
603            Self::PixelValueParseError(e) => {
604                CssStylePerspectiveOriginParseError::PixelValueParseError(e.to_shared())
605            }
606        }
607    }
608}
609
610#[derive(Clone, PartialEq)]
611pub enum CssBackfaceVisibilityParseError<'a> {
612    InvalidValue(&'a str),
613}
614
615impl_debug_as_display!(CssBackfaceVisibilityParseError<'a>);
616impl_display! { CssBackfaceVisibilityParseError<'a>, {
617    InvalidValue(s) => format!("Invalid value for backface-visibility: \"{}\", expected \"visible\" or \"hidden\"", s),
618}}
619
620#[derive(Debug, Clone, PartialEq)]
621pub enum CssBackfaceVisibilityParseErrorOwned {
622    InvalidValue(String),
623}
624
625impl<'a> CssBackfaceVisibilityParseError<'a> {
626    pub fn to_contained(&self) -> CssBackfaceVisibilityParseErrorOwned {
627        match self {
628            Self::InvalidValue(s) => {
629                CssBackfaceVisibilityParseErrorOwned::InvalidValue(s.to_string())
630            }
631        }
632    }
633}
634
635impl CssBackfaceVisibilityParseErrorOwned {
636    pub fn to_shared<'a>(&'a self) -> CssBackfaceVisibilityParseError<'a> {
637        match self {
638            Self::InvalidValue(s) => CssBackfaceVisibilityParseError::InvalidValue(s),
639        }
640    }
641}
642
643// -- Parsers --
644
645#[cfg(feature = "parser")]
646pub fn parse_style_transform_vec<'a>(
647    input: &'a str,
648) -> Result<StyleTransformVec, CssStyleTransformParseError<'a>> {
649    crate::props::basic::parse::split_string_respect_whitespace(input)
650        .iter()
651        .map(|i| parse_style_transform(i))
652        .collect::<Result<Vec<_>, _>>()
653        .map(Into::into)
654}
655
656#[cfg(feature = "parser")]
657pub fn parse_style_transform<'a>(
658    input: &'a str,
659) -> Result<StyleTransform, CssStyleTransformParseError<'a>> {
660    let (transform_type, transform_values) = parse_parentheses(
661        input,
662        &[
663            "matrix",
664            "matrix3d",
665            "translate",
666            "translate3d",
667            "translateX",
668            "translateY",
669            "translateZ",
670            "rotate",
671            "rotate3d",
672            "rotateX",
673            "rotateY",
674            "rotateZ",
675            "scale",
676            "scale3d",
677            "scaleX",
678            "scaleY",
679            "scaleZ",
680            "skew",
681            "skewX",
682            "skewY",
683            "perspective",
684        ],
685    )?;
686
687    fn get_numbers<'a>(
688        input: &'a str,
689        expected: usize,
690    ) -> Result<Vec<f32>, CssStyleTransformParseError<'a>> {
691        let numbers: Vec<_> = input
692            .split(',')
693            .map(|s| s.trim().parse::<f32>())
694            .collect::<Result<_, _>>()?;
695        if numbers.len() != expected {
696            Err(CssStyleTransformParseError::WrongNumberOfComponents {
697                expected,
698                got: numbers.len(),
699                input,
700            })
701        } else {
702            Ok(numbers)
703        }
704    }
705
706    match transform_type {
707        "matrix" => {
708            let nums = get_numbers(transform_values, 6)?;
709            Ok(StyleTransform::Matrix(StyleTransformMatrix2D {
710                a: FloatValue::new(nums[0]),
711                b: FloatValue::new(nums[1]),
712                c: FloatValue::new(nums[2]),
713                d: FloatValue::new(nums[3]),
714                tx: FloatValue::new(nums[4]),
715                ty: FloatValue::new(nums[5]),
716            }))
717        }
718        "matrix3d" => {
719            let nums = get_numbers(transform_values, 16)?;
720            Ok(StyleTransform::Matrix3D(StyleTransformMatrix3D {
721                m11: FloatValue::new(nums[0]),
722                m12: FloatValue::new(nums[1]),
723                m13: FloatValue::new(nums[2]),
724                m14: FloatValue::new(nums[3]),
725                m21: FloatValue::new(nums[4]),
726                m22: FloatValue::new(nums[5]),
727                m23: FloatValue::new(nums[6]),
728                m24: FloatValue::new(nums[7]),
729                m31: FloatValue::new(nums[8]),
730                m32: FloatValue::new(nums[9]),
731                m33: FloatValue::new(nums[10]),
732                m34: FloatValue::new(nums[11]),
733                m41: FloatValue::new(nums[12]),
734                m42: FloatValue::new(nums[13]),
735                m43: FloatValue::new(nums[14]),
736                m44: FloatValue::new(nums[15]),
737            }))
738        }
739        "translate" => {
740            let components: Vec<_> = transform_values.split(',').collect();
741
742            // translate() takes exactly 1 or 2 parameters (x, or x and y)
743            if components.len() > 2 {
744                return Err(CssStyleTransformParseError::WrongNumberOfComponents {
745                    expected: 2,
746                    got: components.len(),
747                    input: transform_values,
748                });
749            }
750
751            let x = parse_pixel_value(
752                components
753                    .get(0)
754                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
755                        expected: 2,
756                        got: 0,
757                        input: transform_values,
758                    })?
759                    .trim(),
760            )?;
761            let y = parse_pixel_value(
762                components
763                    .get(1)
764                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
765                        expected: 2,
766                        got: 1,
767                        input: transform_values,
768                    })?
769                    .trim(),
770            )?;
771            Ok(StyleTransform::Translate(StyleTransformTranslate2D {
772                x,
773                y,
774            }))
775        }
776        "translate3d" => {
777            let components: Vec<_> = transform_values.split(',').collect();
778            let x = parse_pixel_value(
779                components
780                    .get(0)
781                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
782                        expected: 3,
783                        got: 0,
784                        input: transform_values,
785                    })?
786                    .trim(),
787            )?;
788            let y = parse_pixel_value(
789                components
790                    .get(1)
791                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
792                        expected: 3,
793                        got: 1,
794                        input: transform_values,
795                    })?
796                    .trim(),
797            )?;
798            let z = parse_pixel_value(
799                components
800                    .get(2)
801                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
802                        expected: 3,
803                        got: 2,
804                        input: transform_values,
805                    })?
806                    .trim(),
807            )?;
808            Ok(StyleTransform::Translate3D(StyleTransformTranslate3D {
809                x,
810                y,
811                z,
812            }))
813        }
814        "translateX" => Ok(StyleTransform::TranslateX(parse_pixel_value(
815            transform_values,
816        )?)),
817        "translateY" => Ok(StyleTransform::TranslateY(parse_pixel_value(
818            transform_values,
819        )?)),
820        "translateZ" => Ok(StyleTransform::TranslateZ(parse_pixel_value(
821            transform_values,
822        )?)),
823        "rotate" => Ok(StyleTransform::Rotate(parse_angle_value(transform_values)?)),
824        "rotate3d" => {
825            let nums = get_numbers(transform_values, 4)?;
826            Ok(StyleTransform::Rotate3D(StyleTransformRotate3D {
827                x: FloatValue::new(nums[0]),
828                y: FloatValue::new(nums[1]),
829                z: FloatValue::new(nums[2]),
830                angle: AngleValue::deg(nums[3]),
831            }))
832        }
833        "rotateX" => Ok(StyleTransform::RotateX(parse_angle_value(
834            transform_values,
835        )?)),
836        "rotateY" => Ok(StyleTransform::RotateY(parse_angle_value(
837            transform_values,
838        )?)),
839        "rotateZ" => Ok(StyleTransform::RotateZ(parse_angle_value(
840            transform_values,
841        )?)),
842        "scale" => {
843            let nums = get_numbers(transform_values, 2)?;
844            Ok(StyleTransform::Scale(StyleTransformScale2D {
845                x: FloatValue::new(nums[0]),
846                y: FloatValue::new(nums[1]),
847            }))
848        }
849        "scale3d" => {
850            let nums = get_numbers(transform_values, 3)?;
851            Ok(StyleTransform::Scale3D(StyleTransformScale3D {
852                x: FloatValue::new(nums[0]),
853                y: FloatValue::new(nums[1]),
854                z: FloatValue::new(nums[2]),
855            }))
856        }
857        "scaleX" => Ok(StyleTransform::ScaleX(PercentageValue::new(
858            transform_values.trim().parse::<f32>()? * 100.0,
859        ))),
860        "scaleY" => Ok(StyleTransform::ScaleY(PercentageValue::new(
861            transform_values.trim().parse::<f32>()? * 100.0,
862        ))),
863        "scaleZ" => Ok(StyleTransform::ScaleZ(PercentageValue::new(
864            transform_values.trim().parse::<f32>()? * 100.0,
865        ))),
866        "skew" => {
867            let components: Vec<_> = transform_values.split(',').collect();
868            let x = parse_angle_value(
869                components
870                    .get(0)
871                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
872                        expected: 2,
873                        got: 0,
874                        input: transform_values,
875                    })?
876                    .trim(),
877            )?;
878            let y = parse_angle_value(
879                components
880                    .get(1)
881                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
882                        expected: 2,
883                        got: 1,
884                        input: transform_values,
885                    })?
886                    .trim(),
887            )?;
888            Ok(StyleTransform::Skew(StyleTransformSkew2D { x, y }))
889        }
890        "skewX" => Ok(StyleTransform::SkewX(parse_angle_value(transform_values)?)),
891        "skewY" => Ok(StyleTransform::SkewY(parse_angle_value(transform_values)?)),
892        "perspective" => Ok(StyleTransform::Perspective(parse_pixel_value(
893            transform_values,
894        )?)),
895        _ => unreachable!(),
896    }
897}
898
899#[cfg(feature = "parser")]
900pub fn parse_style_transform_origin<'a>(
901    input: &'a str,
902) -> Result<StyleTransformOrigin, CssStyleTransformOriginParseError<'a>> {
903    let components: Vec<_> = input.trim().split_whitespace().collect();
904    if components.len() != 2 {
905        return Err(CssStyleTransformOriginParseError::WrongNumberOfComponents {
906            expected: 2,
907            got: components.len(),
908            input,
909        });
910    }
911
912    // Helper to parse position keywords or pixel values
913    fn parse_position_component(
914        s: &str,
915        is_horizontal: bool,
916    ) -> Result<PixelValue, CssPixelValueParseError> {
917        match s.trim() {
918            "left" if is_horizontal => Ok(PixelValue::percent(0.0)),
919            "center" => Ok(PixelValue::percent(50.0)),
920            "right" if is_horizontal => Ok(PixelValue::percent(100.0)),
921            "top" if !is_horizontal => Ok(PixelValue::percent(0.0)),
922            "bottom" if !is_horizontal => Ok(PixelValue::percent(100.0)),
923            _ => parse_pixel_value(s),
924        }
925    }
926
927    let x = parse_position_component(components[0], true)?;
928    let y = parse_position_component(components[1], false)?;
929    Ok(StyleTransformOrigin { x, y })
930}
931
932#[cfg(feature = "parser")]
933pub fn parse_style_perspective_origin<'a>(
934    input: &'a str,
935) -> Result<StylePerspectiveOrigin, CssStylePerspectiveOriginParseError<'a>> {
936    let components: Vec<_> = input.trim().split_whitespace().collect();
937    if components.len() != 2 {
938        return Err(
939            CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
940                expected: 2,
941                got: components.len(),
942                input,
943            },
944        );
945    }
946    let x = parse_pixel_value(components[0])?;
947    let y = parse_pixel_value(components[1])?;
948    Ok(StylePerspectiveOrigin { x, y })
949}
950
951#[cfg(feature = "parser")]
952pub fn parse_style_backface_visibility<'a>(
953    input: &'a str,
954) -> Result<StyleBackfaceVisibility, CssBackfaceVisibilityParseError<'a>> {
955    match input.trim() {
956        "visible" => Ok(StyleBackfaceVisibility::Visible),
957        "hidden" => Ok(StyleBackfaceVisibility::Hidden),
958        _ => Err(CssBackfaceVisibilityParseError::InvalidValue(input)),
959    }
960}
961
962#[cfg(all(test, feature = "parser"))]
963mod tests {
964    use super::*;
965
966    #[test]
967    fn test_parse_transform_vec() {
968        let result =
969            parse_style_transform_vec("translateX(10px) rotate(90deg) scale(0.5, 0.5)").unwrap();
970        assert_eq!(result.len(), 3);
971        assert!(matches!(
972            result.as_slice()[0],
973            StyleTransform::TranslateX(_)
974        ));
975        assert!(matches!(result.as_slice()[1], StyleTransform::Rotate(_)));
976        assert!(matches!(result.as_slice()[2], StyleTransform::Scale(_)));
977    }
978
979    #[test]
980    fn test_parse_transform_functions() {
981        // Translate
982        assert_eq!(
983            parse_style_transform("translateX(50%)").unwrap(),
984            StyleTransform::TranslateX(PixelValue::percent(50.0))
985        );
986        let translate = parse_style_transform("translate(10px, -20px)").unwrap();
987        if let StyleTransform::Translate(t) = translate {
988            assert_eq!(t.x, PixelValue::px(10.0));
989            assert_eq!(t.y, PixelValue::px(-20.0));
990        } else {
991            panic!("Expected Translate");
992        }
993
994        // Scale
995        assert_eq!(
996            parse_style_transform("scaleY(1.2)").unwrap(),
997            StyleTransform::ScaleY(PercentageValue::new(120.0))
998        );
999        let scale = parse_style_transform("scale(2, 0.5)").unwrap();
1000        if let StyleTransform::Scale(s) = scale {
1001            assert_eq!(s.x.get(), 2.0);
1002            assert_eq!(s.y.get(), 0.5);
1003        } else {
1004            panic!("Expected Scale");
1005        }
1006
1007        // Rotate
1008        assert_eq!(
1009            parse_style_transform("rotate(0.25turn)").unwrap(),
1010            StyleTransform::Rotate(AngleValue::turn(0.25))
1011        );
1012
1013        // Skew
1014        assert_eq!(
1015            parse_style_transform("skewX(-10deg)").unwrap(),
1016            StyleTransform::SkewX(AngleValue::deg(-10.0))
1017        );
1018        let skew = parse_style_transform("skew(20deg, 30deg)").unwrap();
1019        if let StyleTransform::Skew(s) = skew {
1020            assert_eq!(s.x, AngleValue::deg(20.0));
1021            assert_eq!(s.y, AngleValue::deg(30.0));
1022        } else {
1023            panic!("Expected Skew");
1024        }
1025    }
1026
1027    #[test]
1028    fn test_parse_transform_origin() {
1029        let result = parse_style_transform_origin("50% 50%").unwrap();
1030        assert_eq!(result.x, PixelValue::percent(50.0));
1031        assert_eq!(result.y, PixelValue::percent(50.0));
1032
1033        let result = parse_style_transform_origin("left top").unwrap();
1034        assert_eq!(result.x, PixelValue::percent(0.0)); // keywords not yet supported, but parse as 0px
1035        assert_eq!(result.y, PixelValue::percent(0.0));
1036
1037        let result = parse_style_transform_origin("20px bottom").unwrap();
1038        assert_eq!(result.x, PixelValue::px(20.0));
1039        assert_eq!(result.y, PixelValue::percent(100.0)); // keywords not yet supported
1040    }
1041
1042    #[test]
1043    fn test_parse_backface_visibility() {
1044        assert_eq!(
1045            parse_style_backface_visibility("visible").unwrap(),
1046            StyleBackfaceVisibility::Visible
1047        );
1048        assert_eq!(
1049            parse_style_backface_visibility("hidden").unwrap(),
1050            StyleBackfaceVisibility::Hidden
1051        );
1052        assert!(parse_style_backface_visibility("none").is_err());
1053    }
1054
1055    #[test]
1056    fn test_parse_transform_errors() {
1057        // Wrong function name
1058        assert!(parse_style_transform("translatex(10px)").is_err());
1059        // Wrong number of args
1060        assert!(parse_style_transform("scale(1)").is_err());
1061        assert!(parse_style_transform("translate(1, 2, 3)").is_err());
1062        // Invalid value
1063        assert!(parse_style_transform("rotate(10px)").is_err());
1064        assert!(parse_style_transform("translateX(auto)").is_err());
1065    }
1066}