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