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    codegen::format::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    #[must_use] 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    #[must_use] 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::codegen::format::FormatAsRustCode for StylePerspectiveOrigin {
95    fn format_as_rust_code(&self, _tabs: usize) -> String {
96        format!(
97            "StylePerspectiveOrigin {{ x: {}, y: {} }}",
98            crate::codegen::format::format_pixel_value(&self.x),
99            crate::codegen::format::format_pixel_value(&self.y)
100        )
101    }
102}
103
104// Formatting to Rust code for StyleTransformOrigin
105impl crate::codegen::format::FormatAsRustCode for StyleTransformOrigin {
106    fn format_as_rust_code(&self, _tabs: usize) -> String {
107        format!(
108            "StyleTransformOrigin {{ x: {}, y: {} }}",
109            crate::codegen::format::format_pixel_value(&self.x),
110            crate::codegen::format::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            Self::Hidden => "hidden",
128            Self::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, Eq, 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(PrintAsCssValue::print_as_css_value)
184            .collect::<Vec<_>>()
185            .join(" ")
186    }
187}
188
189// Formatting to Rust code for StyleTransformVec
190impl crate::codegen::format::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            Self::Matrix(m) => format!(
203                "matrix({}, {}, {}, {}, {}, {})",
204                m.a, m.b, m.c, m.d, m.tx, m.ty
205            ),
206            Self::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            Self::Translate(t) => format!("translate({}, {})", t.x, t.y),
226            Self::Translate3D(t) => format!("translate3d({}, {}, {})", t.x, t.y, t.z),
227            Self::TranslateX(x) => format!("translateX({x})"),
228            Self::TranslateY(y) => format!("translateY({y})"),
229            Self::TranslateZ(z) => format!("translateZ({z})"),
230            Self::Rotate(r) => format!("rotate({r})"),
231            Self::Rotate3D(r) => {
232                format!("rotate3d({}, {}, {}, {})", r.x, r.y, r.z, r.angle)
233            }
234            Self::RotateX(x) => format!("rotateX({x})"),
235            Self::RotateY(y) => format!("rotateY({y})"),
236            Self::RotateZ(z) => format!("rotateZ({z})"),
237            Self::Scale(s) => format!("scale({}, {})", s.x, s.y),
238            Self::Scale3D(s) => format!("scale3d({}, {}, {})", s.x, s.y, s.z),
239            Self::ScaleX(x) => format!("scaleX({x})"),
240            Self::ScaleY(y) => format!("scaleY({y})"),
241            Self::ScaleZ(z) => format!("scaleZ({z})"),
242            Self::Skew(sk) => format!("skew({}, {})", sk.x, sk.y),
243            Self::SkewX(x) => format!("skewX({x})"),
244            Self::SkewY(y) => format!("skewY({y})"),
245            Self::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, Eq)]
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(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 }
403// Written out (not impl_from!): ParseFloatError carries no lifetime, so the
404// macro's `<'a>` would be used only by the target type (single_use_lifetimes).
405impl From<ParseFloatError> for CssStyleTransformParseError<'_> {
406    fn from(e: ParseFloatError) -> Self {
407        Self::NumberParseError(e)
408    }
409}
410
411impl From<PercentageParseError> for CssStyleTransformParseError<'_> {
412    fn from(p: PercentageParseError) -> Self {
413        Self::PercentageValueParseError(p)
414    }
415}
416
417#[derive(Debug, Clone, PartialEq, Eq)]
418#[repr(C, u8)]
419pub enum CssStyleTransformParseErrorOwned {
420    InvalidTransform(AzString),
421    InvalidParenthesis(ParenthesisParseErrorOwned),
422    WrongNumberOfComponents(WrongComponentCountError),
423    NumberParseError(crate::props::basic::error::ParseFloatError),
424    PixelValueParseError(CssPixelValueParseErrorOwned),
425    AngleValueParseError(CssAngleValueParseErrorOwned),
426    PercentageValueParseError(PercentageParseError),
427}
428
429impl CssStyleTransformParseError<'_> {
430    #[must_use] pub fn to_contained(&self) -> CssStyleTransformParseErrorOwned {
431        match self {
432            Self::InvalidTransform(s) => {
433                CssStyleTransformParseErrorOwned::InvalidTransform((*s).to_string().into())
434            }
435            Self::InvalidParenthesis(e) => {
436                CssStyleTransformParseErrorOwned::InvalidParenthesis(e.to_contained())
437            }
438            Self::WrongNumberOfComponents {
439                expected,
440                got,
441                input,
442            } => CssStyleTransformParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
443                expected: *expected,
444                got: *got,
445                input: (*input).to_string().into(),
446            }),
447            Self::NumberParseError(e) => {
448                CssStyleTransformParseErrorOwned::NumberParseError(e.clone().into())
449            }
450            Self::PixelValueParseError(e) => {
451                CssStyleTransformParseErrorOwned::PixelValueParseError(e.to_contained())
452            }
453            Self::AngleValueParseError(e) => {
454                CssStyleTransformParseErrorOwned::AngleValueParseError(e.to_contained())
455            }
456            Self::PercentageValueParseError(e) => {
457                CssStyleTransformParseErrorOwned::PercentageValueParseError(e.clone())
458            }
459        }
460    }
461}
462
463impl CssStyleTransformParseErrorOwned {
464    #[must_use] pub fn to_shared(&self) -> CssStyleTransformParseError<'_> {
465        match self {
466            Self::InvalidTransform(s) => CssStyleTransformParseError::InvalidTransform(s),
467            Self::InvalidParenthesis(e) => {
468                CssStyleTransformParseError::InvalidParenthesis(e.to_shared())
469            }
470            Self::WrongNumberOfComponents(e) => CssStyleTransformParseError::WrongNumberOfComponents {
471                expected: e.expected,
472                got: e.got,
473                input: e.input.as_str(),
474            },
475            Self::NumberParseError(e) => CssStyleTransformParseError::NumberParseError(e.to_std()),
476            Self::PixelValueParseError(e) => {
477                CssStyleTransformParseError::PixelValueParseError(e.to_shared())
478            }
479            Self::AngleValueParseError(e) => {
480                CssStyleTransformParseError::AngleValueParseError(e.to_shared())
481            }
482            Self::PercentageValueParseError(e) => {
483                CssStyleTransformParseError::PercentageValueParseError(e.clone())
484            }
485        }
486    }
487}
488
489#[derive(Clone, PartialEq, Eq)]
490pub enum CssStyleTransformOriginParseError<'a> {
491    WrongNumberOfComponents {
492        expected: usize,
493        got: usize,
494        input: &'a str,
495    },
496    PixelValueParseError(CssPixelValueParseError<'a>),
497}
498
499impl_debug_as_display!(CssStyleTransformOriginParseError<'a>);
500impl_display! { CssStyleTransformOriginParseError<'a>, {
501    WrongNumberOfComponents { expected, got, input } => format!("Invalid number of components: expected {}, got {}: \"{}\"", expected, got, input),
502    PixelValueParseError(e) => format!("Invalid pixel value: {}", e),
503}}
504impl_from! { CssPixelValueParseError<'a>, CssStyleTransformOriginParseError::PixelValueParseError }
505
506#[derive(Debug, Clone, PartialEq, Eq)]
507#[repr(C, u8)]
508pub enum CssStyleTransformOriginParseErrorOwned {
509    WrongNumberOfComponents(WrongComponentCountError),
510    PixelValueParseError(CssPixelValueParseErrorOwned),
511}
512
513impl CssStyleTransformOriginParseError<'_> {
514    #[must_use] pub fn to_contained(&self) -> CssStyleTransformOriginParseErrorOwned {
515        match self {
516            Self::WrongNumberOfComponents {
517                expected,
518                got,
519                input,
520            } => CssStyleTransformOriginParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
521                expected: *expected,
522                got: *got,
523                input: (*input).to_string().into(),
524            }),
525            Self::PixelValueParseError(e) => {
526                CssStyleTransformOriginParseErrorOwned::PixelValueParseError(e.to_contained())
527            }
528        }
529    }
530}
531
532impl CssStyleTransformOriginParseErrorOwned {
533    #[must_use] pub fn to_shared(&self) -> CssStyleTransformOriginParseError<'_> {
534        match self {
535            Self::WrongNumberOfComponents(e) => CssStyleTransformOriginParseError::WrongNumberOfComponents {
536                expected: e.expected,
537                got: e.got,
538                input: e.input.as_str(),
539            },
540            Self::PixelValueParseError(e) => {
541                CssStyleTransformOriginParseError::PixelValueParseError(e.to_shared())
542            }
543        }
544    }
545}
546
547#[derive(Clone, PartialEq, Eq)]
548pub enum CssStylePerspectiveOriginParseError<'a> {
549    WrongNumberOfComponents {
550        expected: usize,
551        got: usize,
552        input: &'a str,
553    },
554    PixelValueParseError(CssPixelValueParseError<'a>),
555}
556
557impl_debug_as_display!(CssStylePerspectiveOriginParseError<'a>);
558impl_display! { CssStylePerspectiveOriginParseError<'a>, {
559    WrongNumberOfComponents { expected, got, input } => format!("Invalid number of components: expected {}, got {}: \"{}\"", expected, got, input),
560    PixelValueParseError(e) => format!("Invalid pixel value: {}", e),
561}}
562impl_from! { CssPixelValueParseError<'a>, CssStylePerspectiveOriginParseError::PixelValueParseError }
563
564#[derive(Debug, Clone, PartialEq, Eq)]
565#[repr(C, u8)]
566pub enum CssStylePerspectiveOriginParseErrorOwned {
567    WrongNumberOfComponents(WrongComponentCountError),
568    PixelValueParseError(CssPixelValueParseErrorOwned),
569}
570
571impl CssStylePerspectiveOriginParseError<'_> {
572    #[must_use] pub fn to_contained(&self) -> CssStylePerspectiveOriginParseErrorOwned {
573        match self {
574            Self::WrongNumberOfComponents {
575                expected,
576                got,
577                input,
578            } => CssStylePerspectiveOriginParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
579                expected: *expected,
580                got: *got,
581                input: (*input).to_string().into(),
582            }),
583            Self::PixelValueParseError(e) => {
584                CssStylePerspectiveOriginParseErrorOwned::PixelValueParseError(e.to_contained())
585            }
586        }
587    }
588}
589
590impl CssStylePerspectiveOriginParseErrorOwned {
591    #[must_use] pub fn to_shared(&self) -> CssStylePerspectiveOriginParseError<'_> {
592        match self {
593            Self::WrongNumberOfComponents(e) => CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
594                expected: e.expected,
595                got: e.got,
596                input: e.input.as_str(),
597            },
598            Self::PixelValueParseError(e) => {
599                CssStylePerspectiveOriginParseError::PixelValueParseError(e.to_shared())
600            }
601        }
602    }
603}
604
605#[derive(Clone, PartialEq, Eq)]
606pub enum CssBackfaceVisibilityParseError<'a> {
607    InvalidValue(&'a str),
608}
609
610impl_debug_as_display!(CssBackfaceVisibilityParseError<'a>);
611impl_display! { CssBackfaceVisibilityParseError<'a>, {
612    InvalidValue(s) => format!("Invalid value for backface-visibility: \"{}\", expected \"visible\" or \"hidden\"", s),
613}}
614
615#[derive(Debug, Clone, PartialEq, Eq)]
616#[repr(C, u8)]
617pub enum CssBackfaceVisibilityParseErrorOwned {
618    InvalidValue(AzString),
619}
620
621impl CssBackfaceVisibilityParseError<'_> {
622    #[must_use] pub fn to_contained(&self) -> CssBackfaceVisibilityParseErrorOwned {
623        match self {
624            Self::InvalidValue(s) => {
625                CssBackfaceVisibilityParseErrorOwned::InvalidValue((*s).to_string().into())
626            }
627        }
628    }
629}
630
631impl CssBackfaceVisibilityParseErrorOwned {
632    #[must_use] pub fn to_shared(&self) -> CssBackfaceVisibilityParseError<'_> {
633        match self {
634            Self::InvalidValue(s) => CssBackfaceVisibilityParseError::InvalidValue(s),
635        }
636    }
637}
638
639// -- Parsers --
640
641#[cfg(feature = "parser")]
642/// # Errors
643///
644/// Returns an error if `input` is not a valid CSS `transform-vec` value.
645pub fn parse_style_transform_vec(
646    input: &str,
647) -> Result<StyleTransformVec, CssStyleTransformParseError<'_>> {
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")]
656#[allow(clippy::too_many_lines)] // large but cohesive: single-purpose CSS parser/formatter/dispatch table (one branch per property/variant)
657/// # Errors
658///
659/// Returns an error if `input` is not a valid CSS `transform` value.
660pub fn parse_style_transform(
661    input: &str,
662) -> Result<StyleTransform, CssStyleTransformParseError<'_>> {
663    fn get_numbers(
664        input: &str,
665        expected: usize,
666    ) -> Result<Vec<f32>, CssStyleTransformParseError<'_>> {
667        let numbers: Vec<_> = input
668            .split(',')
669            .map(|s| s.trim().parse::<f32>())
670            .collect::<Result<_, _>>()?;
671        if numbers.len() == expected {
672            Ok(numbers)
673        } else {
674            Err(CssStyleTransformParseError::WrongNumberOfComponents {
675                expected,
676                got: numbers.len(),
677                input,
678            })
679        }
680    }
681
682    let (transform_type, transform_values) = parse_parentheses(
683        input,
684        &[
685            "matrix",
686            "matrix3d",
687            "translate",
688            "translate3d",
689            "translateX",
690            "translateY",
691            "translateZ",
692            "rotate",
693            "rotate3d",
694            "rotateX",
695            "rotateY",
696            "rotateZ",
697            "scale",
698            "scale3d",
699            "scaleX",
700            "scaleY",
701            "scaleZ",
702            "skew",
703            "skewX",
704            "skewY",
705            "perspective",
706        ],
707    )?;
708
709    match transform_type {
710        "matrix" => {
711            let nums = get_numbers(transform_values, 6)?;
712            Ok(StyleTransform::Matrix(StyleTransformMatrix2D {
713                a: FloatValue::new(nums[0]),
714                b: FloatValue::new(nums[1]),
715                c: FloatValue::new(nums[2]),
716                d: FloatValue::new(nums[3]),
717                tx: FloatValue::new(nums[4]),
718                ty: FloatValue::new(nums[5]),
719            }))
720        }
721        "matrix3d" => {
722            let nums = get_numbers(transform_values, 16)?;
723            Ok(StyleTransform::Matrix3D(StyleTransformMatrix3D {
724                m11: FloatValue::new(nums[0]),
725                m12: FloatValue::new(nums[1]),
726                m13: FloatValue::new(nums[2]),
727                m14: FloatValue::new(nums[3]),
728                m21: FloatValue::new(nums[4]),
729                m22: FloatValue::new(nums[5]),
730                m23: FloatValue::new(nums[6]),
731                m24: FloatValue::new(nums[7]),
732                m31: FloatValue::new(nums[8]),
733                m32: FloatValue::new(nums[9]),
734                m33: FloatValue::new(nums[10]),
735                m34: FloatValue::new(nums[11]),
736                m41: FloatValue::new(nums[12]),
737                m42: FloatValue::new(nums[13]),
738                m43: FloatValue::new(nums[14]),
739                m44: FloatValue::new(nums[15]),
740            }))
741        }
742        "translate" => {
743            let components: Vec<_> = transform_values.split(',').collect();
744
745            // translate() takes exactly 1 or 2 parameters (x, or x and y)
746            if components.len() > 2 {
747                return Err(CssStyleTransformParseError::WrongNumberOfComponents {
748                    expected: 2,
749                    got: components.len(),
750                    input: transform_values,
751                });
752            }
753
754            let x = parse_pixel_value(
755                components.first()
756                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
757                        expected: 2,
758                        got: 0,
759                        input: transform_values,
760                    })?
761                    .trim(),
762            )?;
763            let y = match components.get(1) {
764                Some(c) => parse_pixel_value(c.trim())?,
765                None => PixelValue::px(0.0),
766            };
767            Ok(StyleTransform::Translate(StyleTransformTranslate2D {
768                x,
769                y,
770            }))
771        }
772        "translate3d" => {
773            let components: Vec<_> = transform_values.split(',').collect();
774            let x = parse_pixel_value(
775                components.first()
776                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
777                        expected: 3,
778                        got: 0,
779                        input: transform_values,
780                    })?
781                    .trim(),
782            )?;
783            let y = parse_pixel_value(
784                components
785                    .get(1)
786                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
787                        expected: 3,
788                        got: 1,
789                        input: transform_values,
790                    })?
791                    .trim(),
792            )?;
793            let z = parse_pixel_value(
794                components
795                    .get(2)
796                    .ok_or(CssStyleTransformParseError::WrongNumberOfComponents {
797                        expected: 3,
798                        got: 2,
799                        input: transform_values,
800                    })?
801                    .trim(),
802            )?;
803            Ok(StyleTransform::Translate3D(StyleTransformTranslate3D {
804                x,
805                y,
806                z,
807            }))
808        }
809        "translateX" => Ok(StyleTransform::TranslateX(parse_pixel_value(
810            transform_values,
811        )?)),
812        "translateY" => Ok(StyleTransform::TranslateY(parse_pixel_value(
813            transform_values,
814        )?)),
815        "translateZ" => Ok(StyleTransform::TranslateZ(parse_pixel_value(
816            transform_values,
817        )?)),
818        "rotate" => Ok(StyleTransform::Rotate(parse_angle_value(transform_values)?)),
819        "rotate3d" => {
820            let parts: Vec<_> = transform_values.splitn(4, ',').collect();
821            if parts.len() != 4 {
822                return Err(CssStyleTransformParseError::WrongNumberOfComponents {
823                    expected: 4,
824                    got: parts.len(),
825                    input: transform_values,
826                });
827            }
828            let x = parts[0].trim().parse::<f32>()?;
829            let y = parts[1].trim().parse::<f32>()?;
830            let z = parts[2].trim().parse::<f32>()?;
831            let angle = parse_angle_value(parts[3].trim())?;
832            Ok(StyleTransform::Rotate3D(StyleTransformRotate3D {
833                x: FloatValue::new(x),
834                y: FloatValue::new(y),
835                z: FloatValue::new(z),
836                angle,
837            }))
838        }
839        "rotateX" => Ok(StyleTransform::RotateX(parse_angle_value(
840            transform_values,
841        )?)),
842        "rotateY" => Ok(StyleTransform::RotateY(parse_angle_value(
843            transform_values,
844        )?)),
845        "rotateZ" => Ok(StyleTransform::RotateZ(parse_angle_value(
846            transform_values,
847        )?)),
848        "scale" => {
849            let parts: Vec<_> = transform_values.split(',').collect();
850            if parts.is_empty() || parts.len() > 2 {
851                return Err(CssStyleTransformParseError::WrongNumberOfComponents {
852                    expected: 2,
853                    got: parts.len(),
854                    input: transform_values,
855                });
856            }
857            let x = parts[0].trim().parse::<f32>()?;
858            let y = if parts.len() == 2 {
859                parts[1].trim().parse::<f32>()?
860            } else {
861                x
862            };
863            Ok(StyleTransform::Scale(StyleTransformScale2D {
864                x: FloatValue::new(x),
865                y: FloatValue::new(y),
866            }))
867        }
868        "scale3d" => {
869            let nums = get_numbers(transform_values, 3)?;
870            Ok(StyleTransform::Scale3D(StyleTransformScale3D {
871                x: FloatValue::new(nums[0]),
872                y: FloatValue::new(nums[1]),
873                z: FloatValue::new(nums[2]),
874            }))
875        }
876        "scaleX" => Ok(StyleTransform::ScaleX(PercentageValue::new(
877            transform_values.trim().parse::<f32>()? * 100.0,
878        ))),
879        "scaleY" => Ok(StyleTransform::ScaleY(PercentageValue::new(
880            transform_values.trim().parse::<f32>()? * 100.0,
881        ))),
882        "scaleZ" => Ok(StyleTransform::ScaleZ(PercentageValue::new(
883            transform_values.trim().parse::<f32>()? * 100.0,
884        ))),
885        "skew" => {
886            let components: Vec<_> = transform_values.split(',').collect();
887            if components.is_empty() || components.len() > 2 {
888                return Err(CssStyleTransformParseError::WrongNumberOfComponents {
889                    expected: 2,
890                    got: components.len(),
891                    input: transform_values,
892                });
893            }
894            let x = parse_angle_value(components[0].trim())?;
895            let y = match components.get(1) {
896                Some(c) => parse_angle_value(c.trim())?,
897                None => AngleValue::deg(0.0),
898            };
899            Ok(StyleTransform::Skew(StyleTransformSkew2D { x, y }))
900        }
901        "skewX" => Ok(StyleTransform::SkewX(parse_angle_value(transform_values)?)),
902        "skewY" => Ok(StyleTransform::SkewY(parse_angle_value(transform_values)?)),
903        "perspective" => Ok(StyleTransform::Perspective(parse_pixel_value(
904            transform_values,
905        )?)),
906        _ => unreachable!(),
907    }
908}
909
910#[cfg(feature = "parser")]
911/// # Errors
912///
913/// Returns an error if `input` is not a valid CSS `transform-origin` value.
914pub fn parse_style_transform_origin(
915    input: &str,
916) -> Result<StyleTransformOrigin, CssStyleTransformOriginParseError<'_>> {
917    // Helper to parse position keywords or pixel values
918    fn parse_position_component(
919        s: &str,
920        is_horizontal: bool,
921    ) -> Result<PixelValue, CssPixelValueParseError<'_>> {
922        match s.trim() {
923            "left" if is_horizontal => Ok(PixelValue::percent(0.0)),
924            "center" => Ok(PixelValue::percent(50.0)),
925            "right" if is_horizontal => Ok(PixelValue::percent(100.0)),
926            "top" if !is_horizontal => Ok(PixelValue::percent(0.0)),
927            "bottom" if !is_horizontal => Ok(PixelValue::percent(100.0)),
928            _ => parse_pixel_value(s),
929        }
930    }
931
932    let components: Vec<_> = input.split_whitespace().collect();
933    if components.len() != 2 {
934        return Err(CssStyleTransformOriginParseError::WrongNumberOfComponents {
935            expected: 2,
936            got: components.len(),
937            input,
938        });
939    }
940
941    let x = parse_position_component(components[0], true)?;
942    let y = parse_position_component(components[1], false)?;
943    Ok(StyleTransformOrigin { x, y })
944}
945
946#[cfg(feature = "parser")]
947/// # Errors
948///
949/// Returns an error if `input` is not a valid CSS `perspective-origin` value.
950pub fn parse_style_perspective_origin(
951    input: &str,
952) -> Result<StylePerspectiveOrigin, CssStylePerspectiveOriginParseError<'_>> {
953    let components: Vec<_> = input.split_whitespace().collect();
954    if components.len() != 2 {
955        return Err(
956            CssStylePerspectiveOriginParseError::WrongNumberOfComponents {
957                expected: 2,
958                got: components.len(),
959                input,
960            },
961        );
962    }
963    let x = parse_pixel_value(components[0])?;
964    let y = parse_pixel_value(components[1])?;
965    Ok(StylePerspectiveOrigin { x, y })
966}
967
968#[cfg(feature = "parser")]
969/// # Errors
970///
971/// Returns an error if `input` is not a valid CSS `backface-visibility` value.
972pub fn parse_style_backface_visibility(
973    input: &str,
974) -> Result<StyleBackfaceVisibility, CssBackfaceVisibilityParseError<'_>> {
975    match input.trim() {
976        "visible" => Ok(StyleBackfaceVisibility::Visible),
977        "hidden" => Ok(StyleBackfaceVisibility::Hidden),
978        _ => Err(CssBackfaceVisibilityParseError::InvalidValue(input)),
979    }
980}
981
982#[cfg(all(test, feature = "parser"))]
983mod tests {
984    // Tests assert that parsed values equal the exact source literals.
985    #![allow(clippy::float_cmp)]
986    use super::*;
987
988    #[test]
989    fn test_parse_transform_vec() {
990        let result =
991            parse_style_transform_vec("translateX(10px) rotate(90deg) scale(0.5, 0.5)").unwrap();
992        assert_eq!(result.len(), 3);
993        assert!(matches!(
994            result.as_slice()[0],
995            StyleTransform::TranslateX(_)
996        ));
997        assert!(matches!(result.as_slice()[1], StyleTransform::Rotate(_)));
998        assert!(matches!(result.as_slice()[2], StyleTransform::Scale(_)));
999    }
1000
1001    #[test]
1002    fn test_parse_transform_functions() {
1003        // Translate
1004        assert_eq!(
1005            parse_style_transform("translateX(50%)").unwrap(),
1006            StyleTransform::TranslateX(PixelValue::percent(50.0))
1007        );
1008        let translate = parse_style_transform("translate(10px, -20px)").unwrap();
1009        if let StyleTransform::Translate(t) = translate {
1010            assert_eq!(t.x, PixelValue::px(10.0));
1011            assert_eq!(t.y, PixelValue::px(-20.0));
1012        } else {
1013            panic!("Expected Translate");
1014        }
1015
1016        // Scale
1017        assert_eq!(
1018            parse_style_transform("scaleY(1.2)").unwrap(),
1019            StyleTransform::ScaleY(PercentageValue::new(120.0))
1020        );
1021        let scale = parse_style_transform("scale(2, 0.5)").unwrap();
1022        if let StyleTransform::Scale(s) = scale {
1023            assert_eq!(s.x.get(), 2.0);
1024            assert_eq!(s.y.get(), 0.5);
1025        } else {
1026            panic!("Expected Scale");
1027        }
1028
1029        // Rotate
1030        assert_eq!(
1031            parse_style_transform("rotate(0.25turn)").unwrap(),
1032            StyleTransform::Rotate(AngleValue::turn(0.25))
1033        );
1034
1035        // Skew
1036        assert_eq!(
1037            parse_style_transform("skewX(-10deg)").unwrap(),
1038            StyleTransform::SkewX(AngleValue::deg(-10.0))
1039        );
1040        let skew = parse_style_transform("skew(20deg, 30deg)").unwrap();
1041        if let StyleTransform::Skew(s) = skew {
1042            assert_eq!(s.x, AngleValue::deg(20.0));
1043            assert_eq!(s.y, AngleValue::deg(30.0));
1044        } else {
1045            panic!("Expected Skew");
1046        }
1047    }
1048
1049    #[test]
1050    fn test_parse_transform_origin() {
1051        let result = parse_style_transform_origin("50% 50%").unwrap();
1052        assert_eq!(result.x, PixelValue::percent(50.0));
1053        assert_eq!(result.y, PixelValue::percent(50.0));
1054
1055        let result = parse_style_transform_origin("left top").unwrap();
1056        assert_eq!(result.x, PixelValue::percent(0.0));
1057        assert_eq!(result.y, PixelValue::percent(0.0));
1058
1059        let result = parse_style_transform_origin("20px bottom").unwrap();
1060        assert_eq!(result.x, PixelValue::px(20.0));
1061        assert_eq!(result.y, PixelValue::percent(100.0));
1062    }
1063
1064    #[test]
1065    fn test_parse_backface_visibility() {
1066        assert_eq!(
1067            parse_style_backface_visibility("visible").unwrap(),
1068            StyleBackfaceVisibility::Visible
1069        );
1070        assert_eq!(
1071            parse_style_backface_visibility("hidden").unwrap(),
1072            StyleBackfaceVisibility::Hidden
1073        );
1074        assert!(parse_style_backface_visibility("none").is_err());
1075    }
1076
1077    #[test]
1078    fn test_parse_transform_errors() {
1079        // Wrong function name
1080        assert!(parse_style_transform("translatex(10px)").is_err());
1081        // Wrong number of args
1082        assert!(parse_style_transform("translate(1, 2, 3)").is_err());
1083        // Single-arg forms (CSS spec compliant)
1084        let scale1 = parse_style_transform("scale(2)").unwrap();
1085        if let StyleTransform::Scale(s) = scale1 {
1086            assert_eq!(s.x.get(), 2.0);
1087            assert_eq!(s.y.get(), 2.0);
1088        } else {
1089            panic!("Expected Scale");
1090        }
1091        let translate1 = parse_style_transform("translate(10px)").unwrap();
1092        if let StyleTransform::Translate(t) = translate1 {
1093            assert_eq!(t.x, PixelValue::px(10.0));
1094            assert_eq!(t.y, PixelValue::px(0.0));
1095        } else {
1096            panic!("Expected Translate");
1097        }
1098        let skew1 = parse_style_transform("skew(20deg)").unwrap();
1099        if let StyleTransform::Skew(s) = skew1 {
1100            assert_eq!(s.x, AngleValue::deg(20.0));
1101            assert_eq!(s.y, AngleValue::deg(0.0));
1102        } else {
1103            panic!("Expected Skew");
1104        }
1105        // rotate3d with angle unit
1106        let rot3d = parse_style_transform("rotate3d(1, 0, 0, 45deg)").unwrap();
1107        if let StyleTransform::Rotate3D(r) = rot3d {
1108            assert_eq!(r.x.get(), 1.0);
1109            assert_eq!(r.angle, AngleValue::deg(45.0));
1110        } else {
1111            panic!("Expected Rotate3D");
1112        }
1113        // Invalid value
1114        assert!(parse_style_transform("rotate(10px)").is_err());
1115        assert!(parse_style_transform("translateX(auto)").is_err());
1116    }
1117}