Skip to main content

azul_css/props/style/
filter.rs

1//! CSS properties for graphical effects like blur, drop-shadow, etc.
2
3use alloc::{
4    string::{String, ToString},
5    vec::Vec,
6};
7use core::{fmt, num::ParseFloatError};
8
9#[cfg(feature = "parser")]
10use crate::props::basic::{
11    error::{InvalidValueErr, InvalidValueErrOwned},
12    length::parse_float_value,
13    parse::{parse_parentheses, ParenthesisParseError, ParenthesisParseErrorOwned},
14};
15use crate::{
16    format_rust_code::GetHash,
17    props::{
18        basic::{
19            color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
20            length::{FloatValue, PercentageParseError, PercentageValue},
21            pixel::{
22                parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
23                PixelValue,
24            },
25        },
26        formatter::PrintAsCssValue,
27        style::{
28            box_shadow::{
29                parse_style_box_shadow, CssShadowParseError, CssShadowParseErrorOwned,
30                StyleBoxShadow,
31            },
32            effects::{parse_style_mix_blend_mode, MixBlendModeParseError, StyleMixBlendMode},
33        },
34    },
35};
36
37// --- TYPE DEFINITIONS ---
38
39#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[repr(C, u8)]
41pub enum StyleFilter {
42    Blend(StyleMixBlendMode),
43    Flood(ColorU),
44    Blur(StyleBlur),
45    Opacity(PercentageValue),
46    ColorMatrix(StyleColorMatrix),
47    DropShadow(StyleBoxShadow),
48    ComponentTransfer,
49    Offset(StyleFilterOffset),
50    Composite(StyleCompositeFilter),
51}
52
53impl_vec!(
54    StyleFilter,
55    StyleFilterVec,
56    StyleFilterVecDestructor,
57    StyleFilterVecDestructorType
58);
59impl_vec_clone!(StyleFilter, StyleFilterVec, StyleFilterVecDestructor);
60impl_vec_debug!(StyleFilter, StyleFilterVec);
61impl_vec_eq!(StyleFilter, StyleFilterVec);
62impl_vec_ord!(StyleFilter, StyleFilterVec);
63impl_vec_hash!(StyleFilter, StyleFilterVec);
64impl_vec_partialeq!(StyleFilter, StyleFilterVec);
65impl_vec_partialord!(StyleFilter, StyleFilterVec);
66
67#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
68#[repr(C)]
69pub struct StyleBlur {
70    pub width: PixelValue,
71    pub height: PixelValue,
72}
73
74/// Color matrix with 20 float values for color transformation.
75/// Layout: 4 rows × 5 columns (RGBA + offset for each channel)
76#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77#[repr(C)]
78pub struct StyleColorMatrix {
79    pub m0: FloatValue,
80    pub m1: FloatValue,
81    pub m2: FloatValue,
82    pub m3: FloatValue,
83    pub m4: FloatValue,
84    pub m5: FloatValue,
85    pub m6: FloatValue,
86    pub m7: FloatValue,
87    pub m8: FloatValue,
88    pub m9: FloatValue,
89    pub m10: FloatValue,
90    pub m11: FloatValue,
91    pub m12: FloatValue,
92    pub m13: FloatValue,
93    pub m14: FloatValue,
94    pub m15: FloatValue,
95    pub m16: FloatValue,
96    pub m17: FloatValue,
97    pub m18: FloatValue,
98    pub m19: FloatValue,
99}
100
101impl StyleColorMatrix {
102    /// Returns the matrix values as a slice for iteration
103    pub fn as_slice(&self) -> [FloatValue; 20] {
104        [
105            self.m0, self.m1, self.m2, self.m3, self.m4, self.m5, self.m6, self.m7, self.m8,
106            self.m9, self.m10, self.m11, self.m12, self.m13, self.m14, self.m15, self.m16,
107            self.m17, self.m18, self.m19,
108        ]
109    }
110}
111
112#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
113#[repr(C)]
114pub struct StyleFilterOffset {
115    pub x: PixelValue,
116    pub y: PixelValue,
117}
118
119/// Arithmetic coefficients for composite filter (k1, k2, k3, k4).
120/// Result = k1*i1*i2 + k2*i1 + k3*i2 + k4
121#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
122#[repr(C)]
123pub struct ArithmeticCoefficients {
124    pub k1: FloatValue,
125    pub k2: FloatValue,
126    pub k3: FloatValue,
127    pub k4: FloatValue,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
131#[repr(C, u8)]
132pub enum StyleCompositeFilter {
133    Over,
134    In,
135    Atop,
136    Out,
137    Xor,
138    Lighter,
139    Arithmetic(ArithmeticCoefficients),
140}
141
142// --- PRINTING IMPLEMENTATIONS ---
143
144impl PrintAsCssValue for StyleFilterVec {
145    fn print_as_css_value(&self) -> String {
146        self.as_ref()
147            .iter()
148            .map(|f| f.print_as_css_value())
149            .collect::<Vec<_>>()
150            .join(" ")
151    }
152}
153
154// Formatting to Rust code for StyleFilterVec
155impl crate::format_rust_code::FormatAsRustCode for StyleFilterVec {
156    fn format_as_rust_code(&self, _tabs: usize) -> String {
157        format!(
158            "StyleFilterVec::from_const_slice(STYLE_FILTER_{}_ITEMS)",
159            self.get_hash()
160        )
161    }
162}
163
164impl PrintAsCssValue for StyleFilter {
165    fn print_as_css_value(&self) -> String {
166        match self {
167            StyleFilter::Blend(mode) => format!("blend({})", mode.print_as_css_value()),
168            StyleFilter::Flood(c) => format!("flood({})", c.to_hash()),
169            StyleFilter::Blur(c) => {
170                if c.width == c.height {
171                    format!("blur({})", c.width)
172                } else {
173                    format!("blur({} {})", c.width, c.height)
174                }
175            }
176            StyleFilter::Opacity(c) => format!("opacity({})", c),
177            StyleFilter::ColorMatrix(c) => format!(
178                "color-matrix({})",
179                c.as_slice()
180                    .iter()
181                    .map(|s| format!("{}", s))
182                    .collect::<Vec<_>>()
183                    .join(" ")
184            ),
185            StyleFilter::DropShadow(shadow) => {
186                format!("drop-shadow({})", shadow.print_as_css_value())
187            }
188            StyleFilter::ComponentTransfer => "component-transfer".to_string(),
189            StyleFilter::Offset(o) => format!("offset({} {})", o.x, o.y),
190            StyleFilter::Composite(c) => format!("composite({})", c.print_as_css_value()),
191        }
192    }
193}
194
195impl PrintAsCssValue for StyleCompositeFilter {
196    fn print_as_css_value(&self) -> String {
197        match self {
198            StyleCompositeFilter::Over => "over".to_string(),
199            StyleCompositeFilter::In => "in".to_string(),
200            StyleCompositeFilter::Atop => "atop".to_string(),
201            StyleCompositeFilter::Out => "out".to_string(),
202            StyleCompositeFilter::Xor => "xor".to_string(),
203            StyleCompositeFilter::Lighter => "lighter".to_string(),
204            StyleCompositeFilter::Arithmetic(fv) => {
205                format!("arithmetic {} {} {} {}", fv.k1, fv.k2, fv.k3, fv.k4)
206            }
207        }
208    }
209}
210
211// --- PARSER ---
212
213#[cfg(feature = "parser")]
214mod parser {
215    use super::*;
216    use crate::props::basic::parse_percentage_value;
217
218    // -- Top-level Filter Error --
219
220    #[derive(Clone, PartialEq)]
221    pub enum CssStyleFilterParseError<'a> {
222        InvalidFilter(&'a str),
223        InvalidParenthesis(ParenthesisParseError<'a>),
224        Shadow(CssShadowParseError<'a>),
225        BlendMode(InvalidValueErr<'a>),
226        Color(CssColorParseError<'a>),
227        Opacity(PercentageParseError),
228        Blur(CssStyleBlurParseError<'a>),
229        ColorMatrix(CssStyleColorMatrixParseError<'a>),
230        Offset(CssStyleFilterOffsetParseError<'a>),
231        Composite(CssStyleCompositeFilterParseError<'a>),
232    }
233
234    impl_debug_as_display!(CssStyleFilterParseError<'a>);
235    impl_display! { CssStyleFilterParseError<'a>, {
236        InvalidFilter(e) => format!("Invalid filter function: \"{}\"", e),
237        InvalidParenthesis(e) => format!("Invalid filter syntax - parenthesis error: {}", e),
238        Shadow(e) => format!("Error parsing drop-shadow(): {}", e),
239        BlendMode(e) => format!("Error parsing blend(): invalid value \"{}\"", e.0),
240        Color(e) => format!("Error parsing flood(): {}", e),
241        Opacity(e) => format!("Error parsing opacity(): {}", e),
242        Blur(e) => format!("Error parsing blur(): {}", e),
243        ColorMatrix(e) => format!("Error parsing color-matrix(): {}", e),
244        Offset(e) => format!("Error parsing offset(): {}", e),
245        Composite(e) => format!("Error parsing composite(): {}", e),
246    }}
247
248    impl_from!(
249        ParenthesisParseError<'a>,
250        CssStyleFilterParseError::InvalidParenthesis
251    );
252    impl_from!(InvalidValueErr<'a>, CssStyleFilterParseError::BlendMode);
253    impl_from!(CssStyleBlurParseError<'a>, CssStyleFilterParseError::Blur);
254    impl_from!(CssColorParseError<'a>, CssStyleFilterParseError::Color);
255    impl_from!(
256        CssStyleColorMatrixParseError<'a>,
257        CssStyleFilterParseError::ColorMatrix
258    );
259    impl_from!(
260        CssStyleFilterOffsetParseError<'a>,
261        CssStyleFilterParseError::Offset
262    );
263    impl_from!(
264        CssStyleCompositeFilterParseError<'a>,
265        CssStyleFilterParseError::Composite
266    );
267    impl_from!(CssShadowParseError<'a>, CssStyleFilterParseError::Shadow);
268
269    impl<'a> From<PercentageParseError> for CssStyleFilterParseError<'a> {
270        fn from(p: PercentageParseError) -> Self {
271            Self::Opacity(p)
272        }
273    }
274
275    impl<'a> From<MixBlendModeParseError<'a>> for CssStyleFilterParseError<'a> {
276        fn from(e: MixBlendModeParseError<'a>) -> Self {
277            // Extract the InvalidValueErr from the MixBlendModeParseError
278            match e {
279                MixBlendModeParseError::InvalidValue(err) => Self::BlendMode(err),
280            }
281        }
282    }
283
284    #[derive(Debug, Clone, PartialEq)]
285    pub enum CssStyleFilterParseErrorOwned {
286        InvalidFilter(String),
287        InvalidParenthesis(ParenthesisParseErrorOwned),
288        Shadow(CssShadowParseErrorOwned),
289        BlendMode(InvalidValueErrOwned),
290        Color(CssColorParseErrorOwned),
291        Opacity(PercentageParseError),
292        Blur(CssStyleBlurParseErrorOwned),
293        ColorMatrix(CssStyleColorMatrixParseErrorOwned),
294        Offset(CssStyleFilterOffsetParseErrorOwned),
295        Composite(CssStyleCompositeFilterParseErrorOwned),
296    }
297
298    impl<'a> CssStyleFilterParseError<'a> {
299        pub fn to_contained(&self) -> CssStyleFilterParseErrorOwned {
300            match self {
301                Self::InvalidFilter(s) => {
302                    CssStyleFilterParseErrorOwned::InvalidFilter(s.to_string())
303                }
304                Self::InvalidParenthesis(e) => {
305                    CssStyleFilterParseErrorOwned::InvalidParenthesis(e.to_contained())
306                }
307                Self::Shadow(e) => CssStyleFilterParseErrorOwned::Shadow(e.to_contained()),
308                Self::BlendMode(e) => CssStyleFilterParseErrorOwned::BlendMode(e.to_contained()),
309                Self::Color(e) => CssStyleFilterParseErrorOwned::Color(e.to_contained()),
310                Self::Opacity(e) => CssStyleFilterParseErrorOwned::Opacity(e.clone()),
311                Self::Blur(e) => CssStyleFilterParseErrorOwned::Blur(e.to_contained()),
312                Self::ColorMatrix(e) => {
313                    CssStyleFilterParseErrorOwned::ColorMatrix(e.to_contained())
314                }
315                Self::Offset(e) => CssStyleFilterParseErrorOwned::Offset(e.to_contained()),
316                Self::Composite(e) => CssStyleFilterParseErrorOwned::Composite(e.to_contained()),
317            }
318        }
319    }
320
321    impl CssStyleFilterParseErrorOwned {
322        pub fn to_shared<'a>(&'a self) -> CssStyleFilterParseError<'a> {
323            match self {
324                Self::InvalidFilter(s) => CssStyleFilterParseError::InvalidFilter(s),
325                Self::InvalidParenthesis(e) => {
326                    CssStyleFilterParseError::InvalidParenthesis(e.to_shared())
327                }
328                Self::Shadow(e) => CssStyleFilterParseError::Shadow(e.to_shared()),
329                Self::BlendMode(e) => CssStyleFilterParseError::BlendMode(e.to_shared()),
330                Self::Color(e) => CssStyleFilterParseError::Color(e.to_shared()),
331                Self::Opacity(e) => CssStyleFilterParseError::Opacity(e.clone()),
332                Self::Blur(e) => CssStyleFilterParseError::Blur(e.to_shared()),
333                Self::ColorMatrix(e) => CssStyleFilterParseError::ColorMatrix(e.to_shared()),
334                Self::Offset(e) => CssStyleFilterParseError::Offset(e.to_shared()),
335                Self::Composite(e) => CssStyleFilterParseError::Composite(e.to_shared()),
336            }
337        }
338    }
339
340    // -- Sub-Errors for each filter function --
341
342    #[derive(Clone, PartialEq)]
343    pub enum CssStyleBlurParseError<'a> {
344        Pixel(CssPixelValueParseError<'a>),
345        TooManyComponents(&'a str),
346    }
347
348    impl_debug_as_display!(CssStyleBlurParseError<'a>);
349    impl_display! { CssStyleBlurParseError<'a>, {
350        Pixel(e) => format!("Invalid pixel value: {}", e),
351        TooManyComponents(input) => format!("Expected 1 or 2 components, got more: \"{}\"", input),
352    }}
353    impl_from!(CssPixelValueParseError<'a>, CssStyleBlurParseError::Pixel);
354
355    #[derive(Debug, Clone, PartialEq)]
356    pub enum CssStyleBlurParseErrorOwned {
357        Pixel(CssPixelValueParseErrorOwned),
358        TooManyComponents(String),
359    }
360
361    impl<'a> CssStyleBlurParseError<'a> {
362        pub fn to_contained(&self) -> CssStyleBlurParseErrorOwned {
363            match self {
364                Self::Pixel(e) => CssStyleBlurParseErrorOwned::Pixel(e.to_contained()),
365                Self::TooManyComponents(s) => {
366                    CssStyleBlurParseErrorOwned::TooManyComponents(s.to_string())
367                }
368            }
369        }
370    }
371
372    impl CssStyleBlurParseErrorOwned {
373        pub fn to_shared<'a>(&'a self) -> CssStyleBlurParseError<'a> {
374            match self {
375                Self::Pixel(e) => CssStyleBlurParseError::Pixel(e.to_shared()),
376                Self::TooManyComponents(s) => CssStyleBlurParseError::TooManyComponents(s),
377            }
378        }
379    }
380
381    #[derive(Clone, PartialEq)]
382    pub enum CssStyleColorMatrixParseError<'a> {
383        Float(ParseFloatError),
384        WrongNumberOfComponents {
385            expected: usize,
386            got: usize,
387            input: &'a str,
388        },
389    }
390
391    impl_debug_as_display!(CssStyleColorMatrixParseError<'a>);
392    impl_display! { CssStyleColorMatrixParseError<'a>, {
393        Float(e) => format!("Error parsing floating-point value: {}", e),
394        WrongNumberOfComponents { expected, got, input } => format!("Expected {} components, got {}: \"{}\"", expected, got, input),
395    }}
396    impl<'a> From<ParseFloatError> for CssStyleColorMatrixParseError<'a> {
397        fn from(p: ParseFloatError) -> Self {
398            Self::Float(p)
399        }
400    }
401
402    #[derive(Debug, Clone, PartialEq)]
403    pub enum CssStyleColorMatrixParseErrorOwned {
404        Float(ParseFloatError),
405        WrongNumberOfComponents {
406            expected: usize,
407            got: usize,
408            input: String,
409        },
410    }
411
412    impl<'a> CssStyleColorMatrixParseError<'a> {
413        pub fn to_contained(&self) -> CssStyleColorMatrixParseErrorOwned {
414            match self {
415                Self::Float(e) => CssStyleColorMatrixParseErrorOwned::Float(e.clone()),
416                Self::WrongNumberOfComponents {
417                    expected,
418                    got,
419                    input,
420                } => CssStyleColorMatrixParseErrorOwned::WrongNumberOfComponents {
421                    expected: *expected,
422                    got: *got,
423                    input: input.to_string(),
424                },
425            }
426        }
427    }
428
429    impl CssStyleColorMatrixParseErrorOwned {
430        pub fn to_shared<'a>(&'a self) -> CssStyleColorMatrixParseError<'a> {
431            match self {
432                Self::Float(e) => CssStyleColorMatrixParseError::Float(e.clone()),
433                Self::WrongNumberOfComponents {
434                    expected,
435                    got,
436                    input,
437                } => CssStyleColorMatrixParseError::WrongNumberOfComponents {
438                    expected: *expected,
439                    got: *got,
440                    input,
441                },
442            }
443        }
444    }
445
446    #[derive(Clone, PartialEq)]
447    pub enum CssStyleFilterOffsetParseError<'a> {
448        Pixel(CssPixelValueParseError<'a>),
449        WrongNumberOfComponents {
450            expected: usize,
451            got: usize,
452            input: &'a str,
453        },
454    }
455
456    impl_debug_as_display!(CssStyleFilterOffsetParseError<'a>);
457    impl_display! { CssStyleFilterOffsetParseError<'a>, {
458        Pixel(e) => format!("Invalid pixel value: {}", e),
459        WrongNumberOfComponents { expected, got, input } => format!("Expected {} components, got {}: \"{}\"", expected, got, input),
460    }}
461    impl_from!(
462        CssPixelValueParseError<'a>,
463        CssStyleFilterOffsetParseError::Pixel
464    );
465
466    #[derive(Debug, Clone, PartialEq)]
467    pub enum CssStyleFilterOffsetParseErrorOwned {
468        Pixel(CssPixelValueParseErrorOwned),
469        WrongNumberOfComponents {
470            expected: usize,
471            got: usize,
472            input: String,
473        },
474    }
475
476    impl<'a> CssStyleFilterOffsetParseError<'a> {
477        pub fn to_contained(&self) -> CssStyleFilterOffsetParseErrorOwned {
478            match self {
479                Self::Pixel(e) => CssStyleFilterOffsetParseErrorOwned::Pixel(e.to_contained()),
480                Self::WrongNumberOfComponents {
481                    expected,
482                    got,
483                    input,
484                } => CssStyleFilterOffsetParseErrorOwned::WrongNumberOfComponents {
485                    expected: *expected,
486                    got: *got,
487                    input: input.to_string(),
488                },
489            }
490        }
491    }
492
493    impl CssStyleFilterOffsetParseErrorOwned {
494        pub fn to_shared<'a>(&'a self) -> CssStyleFilterOffsetParseError<'a> {
495            match self {
496                Self::Pixel(e) => CssStyleFilterOffsetParseError::Pixel(e.to_shared()),
497                Self::WrongNumberOfComponents {
498                    expected,
499                    got,
500                    input,
501                } => CssStyleFilterOffsetParseError::WrongNumberOfComponents {
502                    expected: *expected,
503                    got: *got,
504                    input,
505                },
506            }
507        }
508    }
509
510    #[derive(Clone, PartialEq)]
511    pub enum CssStyleCompositeFilterParseError<'a> {
512        Invalid(InvalidValueErr<'a>),
513        Float(ParseFloatError),
514        WrongNumberOfComponents {
515            expected: usize,
516            got: usize,
517            input: &'a str,
518        },
519    }
520
521    impl_debug_as_display!(CssStyleCompositeFilterParseError<'a>);
522    impl_display! { CssStyleCompositeFilterParseError<'a>, {
523        Invalid(s) => format!("Invalid composite operator: {}", s.0),
524        Float(e) => format!("Error parsing floating-point value for arithmetic(): {}", e),
525        WrongNumberOfComponents { expected, got, input } => format!("Expected {} components for arithmetic(), got {}: \"{}\"", expected, got, input),
526    }}
527    impl_from!(
528        InvalidValueErr<'a>,
529        CssStyleCompositeFilterParseError::Invalid
530    );
531    impl<'a> From<ParseFloatError> for CssStyleCompositeFilterParseError<'a> {
532        fn from(p: ParseFloatError) -> Self {
533            Self::Float(p)
534        }
535    }
536
537    #[derive(Debug, Clone, PartialEq)]
538    pub enum CssStyleCompositeFilterParseErrorOwned {
539        Invalid(InvalidValueErrOwned),
540        Float(ParseFloatError),
541        WrongNumberOfComponents {
542            expected: usize,
543            got: usize,
544            input: String,
545        },
546    }
547
548    impl<'a> CssStyleCompositeFilterParseError<'a> {
549        pub fn to_contained(&self) -> CssStyleCompositeFilterParseErrorOwned {
550            match self {
551                Self::Invalid(e) => {
552                    CssStyleCompositeFilterParseErrorOwned::Invalid(e.to_contained())
553                }
554                Self::Float(e) => CssStyleCompositeFilterParseErrorOwned::Float(e.clone()),
555                Self::WrongNumberOfComponents {
556                    expected,
557                    got,
558                    input,
559                } => CssStyleCompositeFilterParseErrorOwned::WrongNumberOfComponents {
560                    expected: *expected,
561                    got: *got,
562                    input: input.to_string(),
563                },
564            }
565        }
566    }
567
568    impl CssStyleCompositeFilterParseErrorOwned {
569        pub fn to_shared<'a>(&'a self) -> CssStyleCompositeFilterParseError<'a> {
570            match self {
571                Self::Invalid(e) => CssStyleCompositeFilterParseError::Invalid(e.to_shared()),
572                Self::Float(e) => CssStyleCompositeFilterParseError::Float(e.clone()),
573                Self::WrongNumberOfComponents {
574                    expected,
575                    got,
576                    input,
577                } => CssStyleCompositeFilterParseError::WrongNumberOfComponents {
578                    expected: *expected,
579                    got: *got,
580                    input,
581                },
582            }
583        }
584    }
585
586    // -- Parser Implementation --
587
588    /// Parses a space-separated list of filter functions.
589    pub fn parse_style_filter_vec<'a>(
590        input: &'a str,
591    ) -> Result<StyleFilterVec, CssStyleFilterParseError<'a>> {
592        let mut filters = Vec::new();
593        let mut remaining = input.trim();
594        while !remaining.is_empty() {
595            let (filter, rest) = parse_one_filter_function(remaining)?;
596            filters.push(filter);
597            remaining = rest.trim_start();
598        }
599        Ok(filters.into())
600    }
601
602    /// Parses one `function(...)` from the beginning of a string and returns the parsed
603    /// filter and the rest of the string.
604    fn parse_one_filter_function<'a>(
605        input: &'a str,
606    ) -> Result<(StyleFilter, &'a str), CssStyleFilterParseError<'a>> {
607        let open_paren = input
608            .find('(')
609            .ok_or(CssStyleFilterParseError::InvalidFilter(input))?;
610        let func_name = &input[..open_paren];
611
612        let mut balance = 1;
613        let mut close_paren = 0;
614        for (i, c) in input.char_indices().skip(open_paren + 1) {
615            if c == '(' {
616                balance += 1;
617            } else if c == ')' {
618                balance -= 1;
619                if balance == 0 {
620                    close_paren = i;
621                    break;
622                }
623            }
624        }
625
626        if balance != 0 {
627            return Err(ParenthesisParseError::UnclosedBraces.into());
628        }
629
630        let full_function = &input[..=close_paren];
631        let rest = &input[(close_paren + 1)..];
632
633        let filter = parse_style_filter(full_function)?;
634        Ok((filter, rest))
635    }
636
637    /// Parses a single filter function string, like `blur(5px)`.
638    pub fn parse_style_filter<'a>(
639        input: &'a str,
640    ) -> Result<StyleFilter, CssStyleFilterParseError<'a>> {
641        let (filter_type, filter_values) = parse_parentheses(
642            input,
643            &[
644                "blend",
645                "flood",
646                "blur",
647                "opacity",
648                "color-matrix",
649                "drop-shadow",
650                "component-transfer",
651                "offset",
652                "composite",
653            ],
654        )?;
655
656        match filter_type {
657            "blend" => Ok(StyleFilter::Blend(parse_style_mix_blend_mode(
658                filter_values,
659            )?)),
660            "flood" => Ok(StyleFilter::Flood(parse_css_color(filter_values)?)),
661            "blur" => Ok(StyleFilter::Blur(parse_style_blur(filter_values)?)),
662            "opacity" => {
663                let val = parse_percentage_value(filter_values)?;
664                // CSS filter opacity must be between 0 and 1 (or 0% to 100%)
665                let normalized = val.normalized();
666                if normalized < 0.0 || normalized > 1.0 {
667                    return Err(CssStyleFilterParseError::Opacity(
668                        PercentageParseError::InvalidUnit(filter_values.to_string().into()),
669                    ));
670                }
671                Ok(StyleFilter::Opacity(val))
672            }
673            "color-matrix" => Ok(StyleFilter::ColorMatrix(parse_color_matrix(filter_values)?)),
674            "drop-shadow" => Ok(StyleFilter::DropShadow(parse_style_box_shadow(
675                filter_values,
676            )?)),
677            "component-transfer" => Ok(StyleFilter::ComponentTransfer),
678            "offset" => Ok(StyleFilter::Offset(parse_filter_offset(filter_values)?)),
679            "composite" => Ok(StyleFilter::Composite(parse_filter_composite(
680                filter_values,
681            )?)),
682            _ => unreachable!(),
683        }
684    }
685
686    fn parse_style_blur<'a>(input: &'a str) -> Result<StyleBlur, CssStyleBlurParseError<'a>> {
687        let mut iter = input.split_whitespace();
688        let width_str = iter.next().unwrap_or("");
689        let height_str = iter.next();
690
691        if iter.next().is_some() {
692            return Err(CssStyleBlurParseError::TooManyComponents(input));
693        }
694
695        let width = parse_pixel_value(width_str)?;
696        let height = match height_str {
697            Some(s) => parse_pixel_value(s)?,
698            None => width, // If only one value is given, use it for both
699        };
700
701        Ok(StyleBlur { width, height })
702    }
703
704    fn parse_color_matrix<'a>(
705        input: &'a str,
706    ) -> Result<StyleColorMatrix, CssStyleColorMatrixParseError<'a>> {
707        let components: Vec<_> = input.split_whitespace().collect();
708        if components.len() != 20 {
709            return Err(CssStyleColorMatrixParseError::WrongNumberOfComponents {
710                expected: 20,
711                got: components.len(),
712                input,
713            });
714        }
715
716        let mut values = [FloatValue::const_new(0); 20];
717        for (i, comp) in components.iter().enumerate() {
718            values[i] = parse_float_value(comp)?;
719        }
720
721        Ok(StyleColorMatrix {
722            m0: values[0],
723            m1: values[1],
724            m2: values[2],
725            m3: values[3],
726            m4: values[4],
727            m5: values[5],
728            m6: values[6],
729            m7: values[7],
730            m8: values[8],
731            m9: values[9],
732            m10: values[10],
733            m11: values[11],
734            m12: values[12],
735            m13: values[13],
736            m14: values[14],
737            m15: values[15],
738            m16: values[16],
739            m17: values[17],
740            m18: values[18],
741            m19: values[19],
742        })
743    }
744
745    fn parse_filter_offset<'a>(
746        input: &'a str,
747    ) -> Result<StyleFilterOffset, CssStyleFilterOffsetParseError<'a>> {
748        let components: Vec<_> = input.split_whitespace().collect();
749        if components.len() != 2 {
750            return Err(CssStyleFilterOffsetParseError::WrongNumberOfComponents {
751                expected: 2,
752                got: components.len(),
753                input,
754            });
755        }
756
757        let x = parse_pixel_value(components[0])?;
758        let y = parse_pixel_value(components[1])?;
759
760        Ok(StyleFilterOffset { x, y })
761    }
762
763    fn parse_filter_composite<'a>(
764        input: &'a str,
765    ) -> Result<StyleCompositeFilter, CssStyleCompositeFilterParseError<'a>> {
766        let mut iter = input.split_whitespace();
767        let operator = iter.next().unwrap_or("");
768
769        match operator {
770            "over" => Ok(StyleCompositeFilter::Over),
771            "in" => Ok(StyleCompositeFilter::In),
772            "atop" => Ok(StyleCompositeFilter::Atop),
773            "out" => Ok(StyleCompositeFilter::Out),
774            "xor" => Ok(StyleCompositeFilter::Xor),
775            "lighter" => Ok(StyleCompositeFilter::Lighter),
776            "arithmetic" => {
777                let mut values = [FloatValue::const_new(0); 4];
778                for (i, val) in values.iter_mut().enumerate() {
779                    let s = iter.next().ok_or(
780                        CssStyleCompositeFilterParseError::WrongNumberOfComponents {
781                            expected: 4,
782                            got: i,
783                            input,
784                        },
785                    )?;
786                    *val = parse_float_value(s)?;
787                }
788                Ok(StyleCompositeFilter::Arithmetic(ArithmeticCoefficients {
789                    k1: values[0],
790                    k2: values[1],
791                    k3: values[2],
792                    k4: values[3],
793                }))
794            }
795            _ => Err(InvalidValueErr(operator).into()),
796        }
797    }
798}
799#[cfg(feature = "parser")]
800pub use parser::{
801    parse_style_filter_vec, CssStyleBlurParseError, CssStyleBlurParseErrorOwned,
802    CssStyleColorMatrixParseError, CssStyleColorMatrixParseErrorOwned,
803    CssStyleCompositeFilterParseError, CssStyleCompositeFilterParseErrorOwned,
804    CssStyleFilterOffsetParseError, CssStyleFilterOffsetParseErrorOwned, CssStyleFilterParseError,
805    CssStyleFilterParseErrorOwned,
806};
807
808#[cfg(all(test, feature = "parser"))]
809mod tests {
810    use super::*;
811    use crate::props::style::filter::parser::parse_style_filter;
812
813    #[test]
814    fn test_parse_single_filter_functions() {
815        // Blur
816        let blur = parse_style_filter("blur(5px)").unwrap();
817        assert!(matches!(blur, StyleFilter::Blur(_)));
818        if let StyleFilter::Blur(b) = blur {
819            assert_eq!(b.width, PixelValue::px(5.0));
820            assert_eq!(b.height, PixelValue::px(5.0));
821        }
822
823        // Blur with two values
824        let blur2 = parse_style_filter("blur(2px 4px)").unwrap();
825        if let StyleFilter::Blur(b) = blur2 {
826            assert_eq!(b.width, PixelValue::px(2.0));
827            assert_eq!(b.height, PixelValue::px(4.0));
828        }
829
830        // Drop Shadow
831        let shadow = parse_style_filter("drop-shadow(10px 5px 5px #888)").unwrap();
832        assert!(matches!(shadow, StyleFilter::DropShadow(_)));
833        if let StyleFilter::DropShadow(s) = shadow {
834            assert_eq!(s.offset_x.inner, PixelValue::px(10.0));
835            assert_eq!(s.blur_radius.inner, PixelValue::px(5.0));
836            assert_eq!(s.color, ColorU::new_rgb(0x88, 0x88, 0x88));
837        }
838
839        // Opacity
840        let opacity = parse_style_filter("opacity(50%)").unwrap();
841        assert!(matches!(opacity, StyleFilter::Opacity(_)));
842        if let StyleFilter::Opacity(p) = opacity {
843            assert_eq!(p.normalized(), 0.5);
844        }
845
846        // Flood
847        let flood = parse_style_filter("flood(red)").unwrap();
848        assert_eq!(flood, StyleFilter::Flood(ColorU::RED));
849
850        // Composite
851        let composite = parse_style_filter("composite(in)").unwrap();
852        assert_eq!(composite, StyleFilter::Composite(StyleCompositeFilter::In));
853
854        // Offset
855        let offset = parse_style_filter("offset(10px 20%)").unwrap();
856        if let StyleFilter::Offset(o) = offset {
857            assert_eq!(o.x, PixelValue::px(10.0));
858            assert_eq!(o.y, PixelValue::percent(20.0));
859        }
860    }
861
862    #[test]
863    fn test_parse_filter_vec() {
864        let filters =
865            parse_style_filter_vec("blur(5px) drop-shadow(10px 5px #888) opacity(0.8)").unwrap();
866        assert_eq!(filters.len(), 3);
867        assert!(matches!(filters.as_slice()[0], StyleFilter::Blur(_)));
868        assert!(matches!(filters.as_slice()[1], StyleFilter::DropShadow(_)));
869        assert!(matches!(filters.as_slice()[2], StyleFilter::Opacity(_)));
870    }
871
872    #[test]
873    fn test_parse_filter_errors() {
874        // Invalid function name
875        assert!(parse_style_filter_vec("blurry(5px)").is_err());
876        // Incorrect arguments
877        assert!(parse_style_filter_vec("blur(5px 10px 15px)").is_err());
878        assert!(parse_style_filter_vec("opacity(2)").is_err()); // opacity must be % or 0-1
879                                                                // Unclosed parenthesis
880        assert!(parse_style_filter_vec("blur(5px").is_err());
881    }
882}