1
//! CSS properties for graphical effects like blur, drop-shadow, etc.
2
//!
3
//! Defines [`StyleFilter`] and [`StyleFilterVec`] for CSS filter functions
4
//! (blur, opacity, drop-shadow, color-matrix, brightness, contrast, etc.).
5
//! Filters are applied via the WebRender compositor (`compositor2`) or the
6
//! software CPU renderer (`cpurender`).
7

            
8
use alloc::{
9
    string::{String, ToString},
10
    vec::Vec,
11
};
12
use core::{fmt, num::ParseFloatError};
13

            
14
#[cfg(feature = "parser")]
15
use crate::props::basic::{
16
    error::{InvalidValueErr, InvalidValueErrOwned, WrongComponentCountError},
17
    length::parse_float_value,
18
    parse::{parse_parentheses, ParenthesisParseError, ParenthesisParseErrorOwned},
19
};
20
use crate::{
21
    format_rust_code::GetHash,
22
    props::{
23
        basic::{
24
            angle::{AngleValue, parse_angle_value, CssAngleValueParseError, CssAngleValueParseErrorOwned},
25
            color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
26
            length::{FloatValue, PercentageParseError, PercentageValue},
27
            pixel::{
28
                parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
29
                PixelValue,
30
            },
31
        },
32
        formatter::PrintAsCssValue,
33
        style::{
34
            box_shadow::{
35
                parse_style_box_shadow, CssShadowParseError, CssShadowParseErrorOwned,
36
                StyleBoxShadow,
37
            },
38
            effects::{parse_style_mix_blend_mode, MixBlendModeParseError, StyleMixBlendMode},
39
        },
40
    },
41
};
42

            
43
// --- TYPE DEFINITIONS ---
44

            
45
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
46
#[repr(C, u8)]
47
pub enum StyleFilter {
48
    Blend(StyleMixBlendMode),
49
    Flood(ColorU),
50
    Blur(StyleBlur),
51
    Opacity(PercentageValue),
52
    ColorMatrix(StyleColorMatrix),
53
    DropShadow(StyleBoxShadow),
54
    ComponentTransfer,
55
    Offset(StyleFilterOffset),
56
    Composite(StyleCompositeFilter),
57
    // Standard CSS filter functions
58
    Brightness(PercentageValue),
59
    Contrast(PercentageValue),
60
    Grayscale(PercentageValue),
61
    HueRotate(AngleValue),
62
    Invert(PercentageValue),
63
    Saturate(PercentageValue),
64
    Sepia(PercentageValue),
65
}
66

            
67
impl_option!(
68
    StyleFilter,
69
    OptionStyleFilter,
70
    copy = false,
71
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
72
);
73

            
74
impl_vec!(StyleFilter, StyleFilterVec, StyleFilterVecDestructor, StyleFilterVecDestructorType, StyleFilterVecSlice, OptionStyleFilter);
75
impl_vec_clone!(StyleFilter, StyleFilterVec, StyleFilterVecDestructor);
76
impl_vec_debug!(StyleFilter, StyleFilterVec);
77
impl_vec_eq!(StyleFilter, StyleFilterVec);
78
impl_vec_ord!(StyleFilter, StyleFilterVec);
79
impl_vec_hash!(StyleFilter, StyleFilterVec);
80
impl_vec_partialeq!(StyleFilter, StyleFilterVec);
81
impl_vec_partialord!(StyleFilter, StyleFilterVec);
82

            
83
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
84
#[repr(C)]
85
pub struct StyleBlur {
86
    pub width: PixelValue,
87
    pub height: PixelValue,
88
}
89

            
90
/// Color matrix with 20 float values for color transformation.
91
/// Layout: 4 rows × 5 columns (RGBA + offset for each channel)
92
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93
#[repr(C)]
94
pub struct StyleColorMatrix {
95
    pub m0: FloatValue,
96
    pub m1: FloatValue,
97
    pub m2: FloatValue,
98
    pub m3: FloatValue,
99
    pub m4: FloatValue,
100
    pub m5: FloatValue,
101
    pub m6: FloatValue,
102
    pub m7: FloatValue,
103
    pub m8: FloatValue,
104
    pub m9: FloatValue,
105
    pub m10: FloatValue,
106
    pub m11: FloatValue,
107
    pub m12: FloatValue,
108
    pub m13: FloatValue,
109
    pub m14: FloatValue,
110
    pub m15: FloatValue,
111
    pub m16: FloatValue,
112
    pub m17: FloatValue,
113
    pub m18: FloatValue,
114
    pub m19: FloatValue,
115
}
116

            
117
impl StyleColorMatrix {
118
    pub fn to_array(&self) -> [FloatValue; 20] {
119
        [
120
            self.m0, self.m1, self.m2, self.m3, self.m4, self.m5, self.m6, self.m7, self.m8,
121
            self.m9, self.m10, self.m11, self.m12, self.m13, self.m14, self.m15, self.m16,
122
            self.m17, self.m18, self.m19,
123
        ]
124
    }
125
}
126

            
127
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128
#[repr(C)]
129
pub struct StyleFilterOffset {
130
    pub x: PixelValue,
131
    pub y: PixelValue,
132
}
133

            
134
/// Arithmetic coefficients for composite filter (k1, k2, k3, k4).
135
/// Result = k1*i1*i2 + k2*i1 + k3*i2 + k4
136
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
137
#[repr(C)]
138
pub struct ArithmeticCoefficients {
139
    pub k1: FloatValue,
140
    pub k2: FloatValue,
141
    pub k3: FloatValue,
142
    pub k4: FloatValue,
143
}
144

            
145
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
146
#[repr(C, u8)]
147
pub enum StyleCompositeFilter {
148
    Over,
149
    In,
150
    Atop,
151
    Out,
152
    Xor,
153
    Lighter,
154
    Arithmetic(ArithmeticCoefficients),
155
}
156

            
157
// --- PRINTING IMPLEMENTATIONS ---
158

            
159
impl PrintAsCssValue for StyleFilterVec {
160
    fn print_as_css_value(&self) -> String {
161
        self.as_ref()
162
            .iter()
163
            .map(|f| f.print_as_css_value())
164
            .collect::<Vec<_>>()
165
            .join(" ")
166
    }
167
}
168

            
169
// Formatting to Rust code for StyleFilterVec
170
impl crate::format_rust_code::FormatAsRustCode for StyleFilterVec {
171
    fn format_as_rust_code(&self, _tabs: usize) -> String {
172
        format!(
173
            "StyleFilterVec::from_const_slice(STYLE_FILTER_{}_ITEMS)",
174
            self.get_hash()
175
        )
176
    }
177
}
178

            
179
impl PrintAsCssValue for StyleFilter {
180
    fn print_as_css_value(&self) -> String {
181
        match self {
182
            StyleFilter::Blend(mode) => format!("blend({})", mode.print_as_css_value()),
183
            StyleFilter::Flood(c) => format!("flood({})", c.to_hash()),
184
            StyleFilter::Blur(c) => {
185
                if c.width == c.height {
186
                    format!("blur({})", c.width)
187
                } else {
188
                    format!("blur({} {})", c.width, c.height)
189
                }
190
            }
191
            StyleFilter::Opacity(c) => format!("opacity({})", c),
192
            StyleFilter::ColorMatrix(c) => format!(
193
                "color-matrix({})",
194
                c.to_array()
195
                    .iter()
196
                    .map(|s| format!("{}", s))
197
                    .collect::<Vec<_>>()
198
                    .join(" ")
199
            ),
200
            StyleFilter::DropShadow(shadow) => {
201
                format!("drop-shadow({})", shadow.print_as_css_value())
202
            }
203
            StyleFilter::ComponentTransfer => "component-transfer".to_string(),
204
            StyleFilter::Offset(o) => format!("offset({} {})", o.x, o.y),
205
            StyleFilter::Composite(c) => format!("composite({})", c.print_as_css_value()),
206
            StyleFilter::Brightness(v) => format!("brightness({})", v),
207
            StyleFilter::Contrast(v) => format!("contrast({})", v),
208
            StyleFilter::Grayscale(v) => format!("grayscale({})", v),
209
            StyleFilter::HueRotate(a) => format!("hue-rotate({})", a),
210
            StyleFilter::Invert(v) => format!("invert({})", v),
211
            StyleFilter::Saturate(v) => format!("saturate({})", v),
212
            StyleFilter::Sepia(v) => format!("sepia({})", v),
213
        }
214
    }
215
}
216

            
217
impl PrintAsCssValue for StyleCompositeFilter {
218
    fn print_as_css_value(&self) -> String {
219
        match self {
220
            StyleCompositeFilter::Over => "over".to_string(),
221
            StyleCompositeFilter::In => "in".to_string(),
222
            StyleCompositeFilter::Atop => "atop".to_string(),
223
            StyleCompositeFilter::Out => "out".to_string(),
224
            StyleCompositeFilter::Xor => "xor".to_string(),
225
            StyleCompositeFilter::Lighter => "lighter".to_string(),
226
            StyleCompositeFilter::Arithmetic(fv) => {
227
                format!("arithmetic {} {} {} {}", fv.k1, fv.k2, fv.k3, fv.k4)
228
            }
229
        }
230
    }
231
}
232

            
233
// --- PARSER ---
234

            
235
#[cfg(feature = "parser")]
236
pub mod parser {
237
    use super::*;
238
    use crate::props::basic::parse_percentage_value;
239
    use crate::corety::AzString;
240

            
241
    // -- Top-level Filter Error --
242

            
243
    #[derive(Clone, PartialEq)]
244
    pub enum CssStyleFilterParseError<'a> {
245
        InvalidFilter(&'a str),
246
        InvalidParenthesis(ParenthesisParseError<'a>),
247
        Shadow(CssShadowParseError<'a>),
248
        BlendMode(InvalidValueErr<'a>),
249
        Color(CssColorParseError<'a>),
250
        Opacity(PercentageParseError),
251
        Brightness(PercentageParseError),
252
        Contrast(PercentageParseError),
253
        Saturate(PercentageParseError),
254
        Blur(CssStyleBlurParseError<'a>),
255
        ColorMatrix(CssStyleColorMatrixParseError<'a>),
256
        Offset(CssStyleFilterOffsetParseError<'a>),
257
        Composite(CssStyleCompositeFilterParseError<'a>),
258
        Angle(CssAngleValueParseError<'a>),
259
    }
260

            
261
    impl_debug_as_display!(CssStyleFilterParseError<'a>);
262
    impl_display! { CssStyleFilterParseError<'a>, {
263
        InvalidFilter(e) => format!("Invalid filter function: \"{}\"", e),
264
        InvalidParenthesis(e) => format!("Invalid filter syntax - parenthesis error: {}", e),
265
        Shadow(e) => format!("Error parsing drop-shadow(): {}", e),
266
        BlendMode(e) => format!("Error parsing blend(): invalid value \"{}\"", e.0),
267
        Color(e) => format!("Error parsing flood(): {}", e),
268
        Opacity(e) => format!("Error parsing opacity(): {}", e),
269
        Brightness(e) => format!("Error parsing brightness(): {}", e),
270
        Contrast(e) => format!("Error parsing contrast(): {}", e),
271
        Saturate(e) => format!("Error parsing saturate(): {}", e),
272
        Blur(e) => format!("Error parsing blur(): {}", e),
273
        ColorMatrix(e) => format!("Error parsing color-matrix(): {}", e),
274
        Offset(e) => format!("Error parsing offset(): {}", e),
275
        Composite(e) => format!("Error parsing composite(): {}", e),
276
        Angle(e) => format!("Error parsing hue-rotate(): {}", e),
277
    }}
278

            
279
    impl_from!(
280
        ParenthesisParseError<'a>,
281
        CssStyleFilterParseError::InvalidParenthesis
282
    );
283
    impl_from!(InvalidValueErr<'a>, CssStyleFilterParseError::BlendMode);
284
    impl_from!(CssStyleBlurParseError<'a>, CssStyleFilterParseError::Blur);
285
    impl_from!(CssColorParseError<'a>, CssStyleFilterParseError::Color);
286
    impl_from!(
287
        CssStyleColorMatrixParseError<'a>,
288
        CssStyleFilterParseError::ColorMatrix
289
    );
290
    impl_from!(
291
        CssStyleFilterOffsetParseError<'a>,
292
        CssStyleFilterParseError::Offset
293
    );
294
    impl_from!(
295
        CssStyleCompositeFilterParseError<'a>,
296
        CssStyleFilterParseError::Composite
297
    );
298
    impl_from!(CssShadowParseError<'a>, CssStyleFilterParseError::Shadow);
299
    impl_from!(CssAngleValueParseError<'a>, CssStyleFilterParseError::Angle);
300

            
301
    impl<'a> From<PercentageParseError> for CssStyleFilterParseError<'a> {
302
        fn from(p: PercentageParseError) -> Self {
303
            Self::Opacity(p)
304
        }
305
    }
306

            
307
    impl<'a> From<MixBlendModeParseError<'a>> for CssStyleFilterParseError<'a> {
308
        fn from(e: MixBlendModeParseError<'a>) -> Self {
309
            // Extract the InvalidValueErr from the MixBlendModeParseError
310
            match e {
311
                MixBlendModeParseError::InvalidValue(err) => Self::BlendMode(err),
312
            }
313
        }
314
    }
315

            
316
    #[derive(Debug, Clone, PartialEq)]
317
    #[repr(C, u8)]
318
    pub enum CssStyleFilterParseErrorOwned {
319
        InvalidFilter(AzString),
320
        InvalidParenthesis(ParenthesisParseErrorOwned),
321
        Shadow(CssShadowParseErrorOwned),
322
        BlendMode(InvalidValueErrOwned),
323
        Color(CssColorParseErrorOwned),
324
        Opacity(PercentageParseError),
325
        Brightness(PercentageParseError),
326
        Contrast(PercentageParseError),
327
        Saturate(PercentageParseError),
328
        Blur(CssStyleBlurParseErrorOwned),
329
        ColorMatrix(CssStyleColorMatrixParseErrorOwned),
330
        Offset(CssStyleFilterOffsetParseErrorOwned),
331
        Composite(CssStyleCompositeFilterParseErrorOwned),
332
        Angle(CssAngleValueParseErrorOwned),
333
    }
334

            
335
    impl<'a> CssStyleFilterParseError<'a> {
336
        pub fn to_contained(&self) -> CssStyleFilterParseErrorOwned {
337
            match self {
338
                Self::InvalidFilter(s) => {
339
                    CssStyleFilterParseErrorOwned::InvalidFilter(s.to_string().into())
340
                }
341
                Self::InvalidParenthesis(e) => {
342
                    CssStyleFilterParseErrorOwned::InvalidParenthesis(e.to_contained())
343
                }
344
                Self::Shadow(e) => CssStyleFilterParseErrorOwned::Shadow(e.to_contained()),
345
                Self::BlendMode(e) => CssStyleFilterParseErrorOwned::BlendMode(e.to_contained()),
346
                Self::Color(e) => CssStyleFilterParseErrorOwned::Color(e.to_contained()),
347
                Self::Opacity(e) => CssStyleFilterParseErrorOwned::Opacity(e.clone()),
348
                Self::Brightness(e) => CssStyleFilterParseErrorOwned::Brightness(e.clone()),
349
                Self::Contrast(e) => CssStyleFilterParseErrorOwned::Contrast(e.clone()),
350
                Self::Saturate(e) => CssStyleFilterParseErrorOwned::Saturate(e.clone()),
351
                Self::Blur(e) => CssStyleFilterParseErrorOwned::Blur(e.to_contained()),
352
                Self::ColorMatrix(e) => {
353
                    CssStyleFilterParseErrorOwned::ColorMatrix(e.to_contained())
354
                }
355
                Self::Offset(e) => CssStyleFilterParseErrorOwned::Offset(e.to_contained()),
356
                Self::Composite(e) => CssStyleFilterParseErrorOwned::Composite(e.to_contained()),
357
                Self::Angle(e) => CssStyleFilterParseErrorOwned::Angle(e.to_contained()),
358
            }
359
        }
360
    }
361

            
362
    impl CssStyleFilterParseErrorOwned {
363
        pub fn to_shared<'a>(&'a self) -> CssStyleFilterParseError<'a> {
364
            match self {
365
                Self::InvalidFilter(s) => CssStyleFilterParseError::InvalidFilter(s),
366
                Self::InvalidParenthesis(e) => {
367
                    CssStyleFilterParseError::InvalidParenthesis(e.to_shared())
368
                }
369
                Self::Shadow(e) => CssStyleFilterParseError::Shadow(e.to_shared()),
370
                Self::BlendMode(e) => CssStyleFilterParseError::BlendMode(e.to_shared()),
371
                Self::Color(e) => CssStyleFilterParseError::Color(e.to_shared()),
372
                Self::Opacity(e) => CssStyleFilterParseError::Opacity(e.clone()),
373
                Self::Brightness(e) => CssStyleFilterParseError::Brightness(e.clone()),
374
                Self::Contrast(e) => CssStyleFilterParseError::Contrast(e.clone()),
375
                Self::Saturate(e) => CssStyleFilterParseError::Saturate(e.clone()),
376
                Self::Blur(e) => CssStyleFilterParseError::Blur(e.to_shared()),
377
                Self::ColorMatrix(e) => CssStyleFilterParseError::ColorMatrix(e.to_shared()),
378
                Self::Offset(e) => CssStyleFilterParseError::Offset(e.to_shared()),
379
                Self::Composite(e) => CssStyleFilterParseError::Composite(e.to_shared()),
380
                Self::Angle(e) => CssStyleFilterParseError::Angle(e.to_shared()),
381
            }
382
        }
383
    }
384

            
385
    // -- Sub-Errors for each filter function --
386

            
387
    #[derive(Clone, PartialEq)]
388
    pub enum CssStyleBlurParseError<'a> {
389
        Pixel(CssPixelValueParseError<'a>),
390
        TooManyComponents(&'a str),
391
    }
392

            
393
    impl_debug_as_display!(CssStyleBlurParseError<'a>);
394
    impl_display! { CssStyleBlurParseError<'a>, {
395
        Pixel(e) => format!("Invalid pixel value: {}", e),
396
        TooManyComponents(input) => format!("Expected 1 or 2 components, got more: \"{}\"", input),
397
    }}
398
    impl_from!(CssPixelValueParseError<'a>, CssStyleBlurParseError::Pixel);
399

            
400
    #[derive(Debug, Clone, PartialEq)]
401
    #[repr(C, u8)]
402
    pub enum CssStyleBlurParseErrorOwned {
403
        Pixel(CssPixelValueParseErrorOwned),
404
        TooManyComponents(AzString),
405
    }
406

            
407
    impl<'a> CssStyleBlurParseError<'a> {
408
        pub fn to_contained(&self) -> CssStyleBlurParseErrorOwned {
409
            match self {
410
                Self::Pixel(e) => CssStyleBlurParseErrorOwned::Pixel(e.to_contained()),
411
                Self::TooManyComponents(s) => {
412
                    CssStyleBlurParseErrorOwned::TooManyComponents(s.to_string().into())
413
                }
414
            }
415
        }
416
    }
417

            
418
    impl CssStyleBlurParseErrorOwned {
419
        pub fn to_shared<'a>(&'a self) -> CssStyleBlurParseError<'a> {
420
            match self {
421
                Self::Pixel(e) => CssStyleBlurParseError::Pixel(e.to_shared()),
422
                Self::TooManyComponents(s) => CssStyleBlurParseError::TooManyComponents(s),
423
            }
424
        }
425
    }
426

            
427
    #[derive(Clone, PartialEq)]
428
    pub enum CssStyleColorMatrixParseError<'a> {
429
        Float(ParseFloatError),
430
        WrongNumberOfComponents {
431
            expected: usize,
432
            got: usize,
433
            input: &'a str,
434
        },
435
    }
436

            
437
    impl_debug_as_display!(CssStyleColorMatrixParseError<'a>);
438
    impl_display! { CssStyleColorMatrixParseError<'a>, {
439
        Float(e) => format!("Error parsing floating-point value: {}", e),
440
        WrongNumberOfComponents { expected, got, input } => format!("Expected {} components, got {}: \"{}\"", expected, got, input),
441
    }}
442
    impl<'a> From<ParseFloatError> for CssStyleColorMatrixParseError<'a> {
443
        fn from(p: ParseFloatError) -> Self {
444
            Self::Float(p)
445
        }
446
    }
447

            
448
    #[derive(Debug, Clone, PartialEq)]
449
    #[repr(C, u8)]
450
    pub enum CssStyleColorMatrixParseErrorOwned {
451
        Float(crate::props::basic::error::ParseFloatError),
452
        WrongNumberOfComponents(WrongComponentCountError),
453
    }
454

            
455
    impl<'a> CssStyleColorMatrixParseError<'a> {
456
        pub fn to_contained(&self) -> CssStyleColorMatrixParseErrorOwned {
457
            match self {
458
                Self::Float(e) => CssStyleColorMatrixParseErrorOwned::Float(e.clone().into()),
459
                Self::WrongNumberOfComponents {
460
                    expected,
461
                    got,
462
                    input,
463
                } => CssStyleColorMatrixParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
464
                    expected: *expected,
465
                    got: *got,
466
                    input: input.to_string().into(),
467
                }),
468
            }
469
        }
470
    }
471

            
472
    impl CssStyleColorMatrixParseErrorOwned {
473
        pub fn to_shared<'a>(&'a self) -> CssStyleColorMatrixParseError<'a> {
474
            match self {
475
                Self::Float(e) => CssStyleColorMatrixParseError::Float(e.to_std()),
476
                Self::WrongNumberOfComponents(e) => CssStyleColorMatrixParseError::WrongNumberOfComponents {
477
                    expected: e.expected,
478
                    got: e.got,
479
                    input: e.input.as_str(),
480
                },
481
            }
482
        }
483
    }
484

            
485
    #[derive(Clone, PartialEq)]
486
    pub enum CssStyleFilterOffsetParseError<'a> {
487
        Pixel(CssPixelValueParseError<'a>),
488
        WrongNumberOfComponents {
489
            expected: usize,
490
            got: usize,
491
            input: &'a str,
492
        },
493
    }
494

            
495
    impl_debug_as_display!(CssStyleFilterOffsetParseError<'a>);
496
    impl_display! { CssStyleFilterOffsetParseError<'a>, {
497
        Pixel(e) => format!("Invalid pixel value: {}", e),
498
        WrongNumberOfComponents { expected, got, input } => format!("Expected {} components, got {}: \"{}\"", expected, got, input),
499
    }}
500
    impl_from!(
501
        CssPixelValueParseError<'a>,
502
        CssStyleFilterOffsetParseError::Pixel
503
    );
504

            
505
    #[derive(Debug, Clone, PartialEq)]
506
    #[repr(C, u8)]
507
    pub enum CssStyleFilterOffsetParseErrorOwned {
508
        Pixel(CssPixelValueParseErrorOwned),
509
        WrongNumberOfComponents(WrongComponentCountError),
510
    }
511

            
512
    impl<'a> CssStyleFilterOffsetParseError<'a> {
513
        pub fn to_contained(&self) -> CssStyleFilterOffsetParseErrorOwned {
514
            match self {
515
                Self::Pixel(e) => CssStyleFilterOffsetParseErrorOwned::Pixel(e.to_contained()),
516
                Self::WrongNumberOfComponents {
517
                    expected,
518
                    got,
519
                    input,
520
                } => CssStyleFilterOffsetParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
521
                    expected: *expected,
522
                    got: *got,
523
                    input: input.to_string().into(),
524
                }),
525
            }
526
        }
527
    }
528

            
529
    impl CssStyleFilterOffsetParseErrorOwned {
530
        pub fn to_shared<'a>(&'a self) -> CssStyleFilterOffsetParseError<'a> {
531
            match self {
532
                Self::Pixel(e) => CssStyleFilterOffsetParseError::Pixel(e.to_shared()),
533
                Self::WrongNumberOfComponents(e) => CssStyleFilterOffsetParseError::WrongNumberOfComponents {
534
                    expected: e.expected,
535
                    got: e.got,
536
                    input: e.input.as_str(),
537
                },
538
            }
539
        }
540
    }
541

            
542
    #[derive(Clone, PartialEq)]
543
    pub enum CssStyleCompositeFilterParseError<'a> {
544
        Invalid(InvalidValueErr<'a>),
545
        Float(ParseFloatError),
546
        WrongNumberOfComponents {
547
            expected: usize,
548
            got: usize,
549
            input: &'a str,
550
        },
551
    }
552

            
553
    impl_debug_as_display!(CssStyleCompositeFilterParseError<'a>);
554
    impl_display! { CssStyleCompositeFilterParseError<'a>, {
555
        Invalid(s) => format!("Invalid composite operator: {}", s.0),
556
        Float(e) => format!("Error parsing floating-point value for arithmetic(): {}", e),
557
        WrongNumberOfComponents { expected, got, input } => format!("Expected {} components for arithmetic(), got {}: \"{}\"", expected, got, input),
558
    }}
559
    impl_from!(
560
        InvalidValueErr<'a>,
561
        CssStyleCompositeFilterParseError::Invalid
562
    );
563
    impl<'a> From<ParseFloatError> for CssStyleCompositeFilterParseError<'a> {
564
        fn from(p: ParseFloatError) -> Self {
565
            Self::Float(p)
566
        }
567
    }
568

            
569
    #[derive(Debug, Clone, PartialEq)]
570
    #[repr(C, u8)]
571
    pub enum CssStyleCompositeFilterParseErrorOwned {
572
        Invalid(InvalidValueErrOwned),
573
        Float(crate::props::basic::error::ParseFloatError),
574
        WrongNumberOfComponents(WrongComponentCountError),
575
    }
576

            
577
    impl<'a> CssStyleCompositeFilterParseError<'a> {
578
        pub fn to_contained(&self) -> CssStyleCompositeFilterParseErrorOwned {
579
            match self {
580
                Self::Invalid(e) => {
581
                    CssStyleCompositeFilterParseErrorOwned::Invalid(e.to_contained())
582
                }
583
                Self::Float(e) => CssStyleCompositeFilterParseErrorOwned::Float(e.clone().into()),
584
                Self::WrongNumberOfComponents {
585
                    expected,
586
                    got,
587
                    input,
588
                } => CssStyleCompositeFilterParseErrorOwned::WrongNumberOfComponents(WrongComponentCountError {
589
                    expected: *expected,
590
                    got: *got,
591
                    input: input.to_string().into(),
592
                }),
593
            }
594
        }
595
    }
596

            
597
    impl CssStyleCompositeFilterParseErrorOwned {
598
        pub fn to_shared<'a>(&'a self) -> CssStyleCompositeFilterParseError<'a> {
599
            match self {
600
                Self::Invalid(e) => CssStyleCompositeFilterParseError::Invalid(e.to_shared()),
601
                Self::Float(e) => CssStyleCompositeFilterParseError::Float(e.to_std()),
602
                Self::WrongNumberOfComponents(e) => CssStyleCompositeFilterParseError::WrongNumberOfComponents {
603
                    expected: e.expected,
604
                    got: e.got,
605
                    input: e.input.as_str(),
606
                },
607
            }
608
        }
609
    }
610

            
611
    // -- Parser Implementation --
612

            
613
    /// Parses a space-separated list of filter functions.
614
5
    pub fn parse_style_filter_vec<'a>(
615
5
        input: &'a str,
616
5
    ) -> Result<StyleFilterVec, CssStyleFilterParseError<'a>> {
617
5
        let mut filters = Vec::new();
618
5
        let mut remaining = input.trim();
619
8
        while !remaining.is_empty() {
620
7
            let (filter, rest) = parse_one_filter_function(remaining)?;
621
3
            filters.push(filter);
622
3
            remaining = rest.trim_start();
623
        }
624
1
        Ok(filters.into())
625
5
    }
626

            
627
    /// Parses one `function(...)` from the beginning of a string and returns the parsed
628
    /// filter and the rest of the string.
629
7
    fn parse_one_filter_function<'a>(
630
7
        input: &'a str,
631
7
    ) -> Result<(StyleFilter, &'a str), CssStyleFilterParseError<'a>> {
632
7
        let open_paren = input
633
7
            .find('(')
634
7
            .ok_or(CssStyleFilterParseError::InvalidFilter(input))?;
635
7
        let func_name = &input[..open_paren];
636

            
637
7
        let mut balance = 1;
638
7
        let mut close_paren = 0;
639
45
        for (i, c) in input.char_indices().skip(open_paren + 1) {
640
45
            if c == '(' {
641
                balance += 1;
642
45
            } else if c == ')' {
643
6
                balance -= 1;
644
6
                if balance == 0 {
645
6
                    close_paren = i;
646
6
                    break;
647
                }
648
39
            }
649
        }
650

            
651
7
        if balance != 0 {
652
1
            return Err(ParenthesisParseError::UnclosedBraces.into());
653
6
        }
654

            
655
6
        let full_function = &input[..=close_paren];
656
6
        let rest = &input[(close_paren + 1)..];
657

            
658
6
        let filter = parse_style_filter(full_function)?;
659
3
        Ok((filter, rest))
660
7
    }
661

            
662
    /// Parses a single filter function string, like `blur(5px)`.
663
23
    pub fn parse_style_filter<'a>(
664
23
        input: &'a str,
665
23
    ) -> Result<StyleFilter, CssStyleFilterParseError<'a>> {
666
23
        let (filter_type, filter_values) = parse_parentheses(
667
23
            input,
668
23
            &[
669
23
                "blend",
670
23
                "flood",
671
23
                "blur",
672
23
                "opacity",
673
23
                "color-matrix",
674
23
                "drop-shadow",
675
23
                "component-transfer",
676
23
                "offset",
677
23
                "composite",
678
23
                "brightness",
679
23
                "contrast",
680
23
                "grayscale",
681
23
                "hue-rotate",
682
23
                "invert",
683
23
                "saturate",
684
23
                "sepia",
685
23
            ],
686
1
        )?;
687

            
688
22
        match filter_type {
689
22
            "blend" => Ok(StyleFilter::Blend(parse_style_mix_blend_mode(
690
                filter_values,
691
            )?)),
692
22
            "flood" => Ok(StyleFilter::Flood(parse_css_color(filter_values)?)),
693
21
            "blur" => Ok(StyleFilter::Blur(parse_style_blur(filter_values)?)),
694
17
            "opacity" => {
695
3
                let val = parse_percentage_value(filter_values)?;
696
                // CSS filter opacity must be between 0 and 1 (or 0% to 100%)
697
3
                let normalized = val.normalized();
698
3
                if !(0.0..=1.0).contains(&normalized) {
699
1
                    return Err(CssStyleFilterParseError::Opacity(
700
1
                        PercentageParseError::InvalidUnit(filter_values.to_string().into()),
701
1
                    ));
702
2
                }
703
2
                Ok(StyleFilter::Opacity(val))
704
            }
705
14
            "color-matrix" => Ok(StyleFilter::ColorMatrix(parse_color_matrix(filter_values)?)),
706
14
            "drop-shadow" => Ok(StyleFilter::DropShadow(parse_style_box_shadow(
707
2
                filter_values,
708
            )?)),
709
12
            "component-transfer" => Ok(StyleFilter::ComponentTransfer),
710
12
            "offset" => Ok(StyleFilter::Offset(parse_filter_offset(filter_values)?)),
711
11
            "composite" => Ok(StyleFilter::Composite(parse_filter_composite(
712
1
                filter_values,
713
            )?)),
714
10
            "brightness" => {
715
2
                let val = parse_percentage_value(filter_values)?;
716
2
                if val.normalized() < 0.0 {
717
1
                    return Err(CssStyleFilterParseError::Brightness(
718
1
                        PercentageParseError::InvalidUnit(filter_values.to_string().into()),
719
1
                    ));
720
1
                }
721
1
                Ok(StyleFilter::Brightness(val))
722
            }
723
8
            "contrast" => {
724
2
                let val = parse_percentage_value(filter_values)?;
725
2
                if val.normalized() < 0.0 {
726
1
                    return Err(CssStyleFilterParseError::Contrast(
727
1
                        PercentageParseError::InvalidUnit(filter_values.to_string().into()),
728
1
                    ));
729
1
                }
730
1
                Ok(StyleFilter::Contrast(val))
731
            }
732
6
            "grayscale" => Ok(StyleFilter::Grayscale(parse_percentage_value(filter_values)?)),
733
5
            "hue-rotate" => Ok(StyleFilter::HueRotate(parse_angle_value(filter_values)?)),
734
4
            "invert" => Ok(StyleFilter::Invert(parse_percentage_value(filter_values)?)),
735
3
            "saturate" => {
736
2
                let val = parse_percentage_value(filter_values)?;
737
2
                if val.normalized() < 0.0 {
738
1
                    return Err(CssStyleFilterParseError::Saturate(
739
1
                        PercentageParseError::InvalidUnit(filter_values.to_string().into()),
740
1
                    ));
741
1
                }
742
1
                Ok(StyleFilter::Saturate(val))
743
            }
744
1
            "sepia" => Ok(StyleFilter::Sepia(parse_percentage_value(filter_values)?)),
745
            _ => unreachable!(),
746
        }
747
23
    }
748

            
749
4
    fn parse_style_blur<'a>(input: &'a str) -> Result<StyleBlur, CssStyleBlurParseError<'a>> {
750
4
        let mut iter = input.split_whitespace();
751
4
        let width_str = iter.next().unwrap_or("");
752
4
        let height_str = iter.next();
753

            
754
4
        if iter.next().is_some() {
755
1
            return Err(CssStyleBlurParseError::TooManyComponents(input));
756
3
        }
757

            
758
3
        let width = parse_pixel_value(width_str)?;
759
3
        let height = match height_str {
760
1
            Some(s) => parse_pixel_value(s)?,
761
2
            None => width, // If only one value is given, use it for both
762
        };
763

            
764
3
        Ok(StyleBlur { width, height })
765
4
    }
766

            
767
    fn parse_color_matrix<'a>(
768
        input: &'a str,
769
    ) -> Result<StyleColorMatrix, CssStyleColorMatrixParseError<'a>> {
770
        let components: Vec<_> = input.split_whitespace().collect();
771
        if components.len() != 20 {
772
            return Err(CssStyleColorMatrixParseError::WrongNumberOfComponents {
773
                expected: 20,
774
                got: components.len(),
775
                input,
776
            });
777
        }
778

            
779
        let mut values = [FloatValue::const_new(0); 20];
780
        for (i, comp) in components.iter().enumerate() {
781
            values[i] = parse_float_value(comp)?;
782
        }
783

            
784
        Ok(StyleColorMatrix {
785
            m0: values[0],
786
            m1: values[1],
787
            m2: values[2],
788
            m3: values[3],
789
            m4: values[4],
790
            m5: values[5],
791
            m6: values[6],
792
            m7: values[7],
793
            m8: values[8],
794
            m9: values[9],
795
            m10: values[10],
796
            m11: values[11],
797
            m12: values[12],
798
            m13: values[13],
799
            m14: values[14],
800
            m15: values[15],
801
            m16: values[16],
802
            m17: values[17],
803
            m18: values[18],
804
            m19: values[19],
805
        })
806
    }
807

            
808
1
    fn parse_filter_offset<'a>(
809
1
        input: &'a str,
810
1
    ) -> Result<StyleFilterOffset, CssStyleFilterOffsetParseError<'a>> {
811
1
        let components: Vec<_> = input.split_whitespace().collect();
812
1
        if components.len() != 2 {
813
            return Err(CssStyleFilterOffsetParseError::WrongNumberOfComponents {
814
                expected: 2,
815
                got: components.len(),
816
                input,
817
            });
818
1
        }
819

            
820
1
        let x = parse_pixel_value(components[0])?;
821
1
        let y = parse_pixel_value(components[1])?;
822

            
823
1
        Ok(StyleFilterOffset { x, y })
824
1
    }
825

            
826
1
    fn parse_filter_composite<'a>(
827
1
        input: &'a str,
828
1
    ) -> Result<StyleCompositeFilter, CssStyleCompositeFilterParseError<'a>> {
829
1
        let mut iter = input.split_whitespace();
830
1
        let operator = iter.next().unwrap_or("");
831

            
832
1
        match operator {
833
1
            "over" => Ok(StyleCompositeFilter::Over),
834
1
            "in" => Ok(StyleCompositeFilter::In),
835
            "atop" => Ok(StyleCompositeFilter::Atop),
836
            "out" => Ok(StyleCompositeFilter::Out),
837
            "xor" => Ok(StyleCompositeFilter::Xor),
838
            "lighter" => Ok(StyleCompositeFilter::Lighter),
839
            "arithmetic" => {
840
                let mut values = [FloatValue::const_new(0); 4];
841
                for (i, val) in values.iter_mut().enumerate() {
842
                    let s = iter.next().ok_or(
843
                        CssStyleCompositeFilterParseError::WrongNumberOfComponents {
844
                            expected: 4,
845
                            got: i,
846
                            input,
847
                        },
848
                    )?;
849
                    *val = parse_float_value(s)?;
850
                }
851
                Ok(StyleCompositeFilter::Arithmetic(ArithmeticCoefficients {
852
                    k1: values[0],
853
                    k2: values[1],
854
                    k3: values[2],
855
                    k4: values[3],
856
                }))
857
            }
858
            _ => Err(InvalidValueErr(operator).into()),
859
        }
860
1
    }
861
}
862
#[cfg(feature = "parser")]
863
pub use parser::{
864
    parse_style_filter_vec, CssStyleBlurParseError, CssStyleBlurParseErrorOwned,
865
    CssStyleColorMatrixParseError, CssStyleColorMatrixParseErrorOwned,
866
    CssStyleCompositeFilterParseError, CssStyleCompositeFilterParseErrorOwned,
867
    CssStyleFilterOffsetParseError, CssStyleFilterOffsetParseErrorOwned, CssStyleFilterParseError,
868
    CssStyleFilterParseErrorOwned,
869
};
870

            
871
#[cfg(all(test, feature = "parser"))]
872
mod tests {
873
    use super::*;
874
    use crate::props::style::filter::parser::parse_style_filter;
875

            
876
    #[test]
877
1
    fn test_parse_single_filter_functions() {
878
        // Blur
879
1
        let blur = parse_style_filter("blur(5px)").unwrap();
880
1
        assert!(matches!(blur, StyleFilter::Blur(_)));
881
1
        if let StyleFilter::Blur(b) = blur {
882
1
            assert_eq!(b.width, PixelValue::px(5.0));
883
1
            assert_eq!(b.height, PixelValue::px(5.0));
884
        }
885

            
886
        // Blur with two values
887
1
        let blur2 = parse_style_filter("blur(2px 4px)").unwrap();
888
1
        if let StyleFilter::Blur(b) = blur2 {
889
1
            assert_eq!(b.width, PixelValue::px(2.0));
890
1
            assert_eq!(b.height, PixelValue::px(4.0));
891
        }
892

            
893
        // Drop Shadow
894
1
        let shadow = parse_style_filter("drop-shadow(10px 5px 5px #888)").unwrap();
895
1
        assert!(matches!(shadow, StyleFilter::DropShadow(_)));
896
1
        if let StyleFilter::DropShadow(s) = shadow {
897
1
            assert_eq!(s.offset_x.inner, PixelValue::px(10.0));
898
1
            assert_eq!(s.blur_radius.inner, PixelValue::px(5.0));
899
1
            assert_eq!(s.color, ColorU::new_rgb(0x88, 0x88, 0x88));
900
        }
901

            
902
        // Opacity
903
1
        let opacity = parse_style_filter("opacity(50%)").unwrap();
904
1
        assert!(matches!(opacity, StyleFilter::Opacity(_)));
905
1
        if let StyleFilter::Opacity(p) = opacity {
906
1
            assert_eq!(p.normalized(), 0.5);
907
        }
908

            
909
        // Flood
910
1
        let flood = parse_style_filter("flood(red)").unwrap();
911
1
        assert_eq!(flood, StyleFilter::Flood(ColorU::RED));
912

            
913
        // Composite
914
1
        let composite = parse_style_filter("composite(in)").unwrap();
915
1
        assert_eq!(composite, StyleFilter::Composite(StyleCompositeFilter::In));
916

            
917
        // Offset
918
1
        let offset = parse_style_filter("offset(10px 20%)").unwrap();
919
1
        if let StyleFilter::Offset(o) = offset {
920
1
            assert_eq!(o.x, PixelValue::px(10.0));
921
1
            assert_eq!(o.y, PixelValue::percent(20.0));
922
        }
923
1
    }
924

            
925
    #[test]
926
1
    fn test_parse_filter_vec() {
927
1
        let filters =
928
1
            parse_style_filter_vec("blur(5px) drop-shadow(10px 5px #888) opacity(0.8)").unwrap();
929
1
        assert_eq!(filters.len(), 3);
930
1
        assert!(matches!(filters.as_slice()[0], StyleFilter::Blur(_)));
931
1
        assert!(matches!(filters.as_slice()[1], StyleFilter::DropShadow(_)));
932
1
        assert!(matches!(filters.as_slice()[2], StyleFilter::Opacity(_)));
933
1
    }
934

            
935
    #[test]
936
1
    fn test_parse_standard_css_filters() {
937
1
        let brightness = parse_style_filter("brightness(150%)").unwrap();
938
1
        if let StyleFilter::Brightness(v) = brightness {
939
1
            assert!((v.normalized() - 1.5).abs() < 0.001);
940
        } else {
941
            panic!("expected Brightness");
942
        }
943

            
944
1
        let contrast = parse_style_filter("contrast(200%)").unwrap();
945
1
        if let StyleFilter::Contrast(v) = contrast {
946
1
            assert!((v.normalized() - 2.0).abs() < 0.001);
947
        } else {
948
            panic!("expected Contrast");
949
        }
950

            
951
1
        let grayscale = parse_style_filter("grayscale(100%)").unwrap();
952
1
        if let StyleFilter::Grayscale(v) = grayscale {
953
1
            assert!((v.normalized() - 1.0).abs() < 0.001);
954
        } else {
955
            panic!("expected Grayscale");
956
        }
957

            
958
1
        let hue = parse_style_filter("hue-rotate(90deg)").unwrap();
959
1
        assert!(matches!(hue, StyleFilter::HueRotate(_)));
960

            
961
1
        let invert = parse_style_filter("invert(75%)").unwrap();
962
1
        if let StyleFilter::Invert(v) = invert {
963
1
            assert!((v.normalized() - 0.75).abs() < 0.001);
964
        } else {
965
            panic!("expected Invert");
966
        }
967

            
968
1
        let saturate = parse_style_filter("saturate(50%)").unwrap();
969
1
        if let StyleFilter::Saturate(v) = saturate {
970
1
            assert!((v.normalized() - 0.5).abs() < 0.001);
971
        } else {
972
            panic!("expected Saturate");
973
        }
974

            
975
1
        let sepia = parse_style_filter("sepia(60%)").unwrap();
976
1
        if let StyleFilter::Sepia(v) = sepia {
977
1
            assert!((v.normalized() - 0.6).abs() < 0.001);
978
        } else {
979
            panic!("expected Sepia");
980
        }
981
1
    }
982

            
983
    #[test]
984
1
    fn test_negative_values_rejected() {
985
1
        assert!(parse_style_filter("brightness(-50%)").is_err());
986
1
        assert!(parse_style_filter("contrast(-10%)").is_err());
987
1
        assert!(parse_style_filter("saturate(-20%)").is_err());
988
1
    }
989

            
990
    #[test]
991
1
    fn test_parse_filter_errors() {
992
        // Invalid function name
993
1
        assert!(parse_style_filter_vec("blurry(5px)").is_err());
994
        // Incorrect arguments
995
1
        assert!(parse_style_filter_vec("blur(5px 10px 15px)").is_err());
996
1
        assert!(parse_style_filter_vec("opacity(2)").is_err()); // opacity must be % or 0-1
997
                                                                // Unclosed parenthesis
998
1
        assert!(parse_style_filter_vec("blur(5px").is_err());
999
1
    }
}