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