1
//! SVG2 filter function shortcuts - `blur()`, `brightness()`, etc.
2
//!
3
//! The `<filter>` element from SVG1.1 (also present in SVG2) uses some verbose XML to
4
//! define chains of filter primitives.  In SVG2, there is a shortcut form of the `filter`
5
//! attribute and property, where one can simply say `filter="blur(5)"` and get the
6
//! equivalent of writing a full `<filter>` with a `<feGaussianBlur>` element.
7
//!
8
//! This module has a type for each of the filter functions in SVG2 with the function's
9
//! parameters, for example [`Blur`] stores the blur's standard deviation parameter.
10
//!
11
//! Those types get aggregated in the [`FilterFunction`] enum.  A [`FilterFunction`] can
12
//! then convert itself into a [`FilterSpec`], which is ready to be rendered on a surface.
13

            
14
use cssparser::{Color, Parser};
15

            
16
use crate::angle::Angle;
17
use crate::error::*;
18
use crate::filter::Filter;
19
use crate::filters::{
20
    color_matrix::ColorMatrix,
21
    component_transfer::{self, FeFuncA, FeFuncB, FeFuncCommon, FeFuncG, FeFuncR},
22
    composite::{Composite, Operator},
23
    flood::Flood,
24
    gaussian_blur::GaussianBlur,
25
    merge::{Merge, MergeNode},
26
    offset::Offset,
27
    FilterSpec, Input, Primitive, PrimitiveParams, ResolvedPrimitive, UserSpacePrimitive,
28
};
29
use crate::length::*;
30
use crate::paint_server::resolve_color;
31
use crate::parsers::{CustomIdent, NumberOptionalNumber, NumberOrPercentage, Parse};
32
use crate::unit_interval::UnitInterval;
33

            
34
/// CSS Filter functions from the Filter Effects Module Level 1
35
///
36
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#filter-functions>
37
150
#[derive(Debug, Clone, PartialEq)]
38
pub enum FilterFunction {
39
20
    Blur(Blur),
40
8
    Brightness(Brightness),
41
8
    Contrast(Contrast),
42
11
    DropShadow(DropShadow),
43
8
    Grayscale(Grayscale),
44
9
    HueRotate(HueRotate),
45
8
    Invert(Invert),
46
32
    Opacity(Opacity),
47
8
    Saturate(Saturate),
48
11
    Sepia(Sepia),
49
}
50

            
51
/// Parameters for the `blur()` filter function
52
///
53
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-blur>
54
40
#[derive(Debug, Clone, PartialEq)]
55
pub struct Blur {
56
20
    std_deviation: Option<Length<Both>>,
57
}
58

            
59
/// Parameters for the `brightness()` filter function
60
///
61
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-brightness>
62
16
#[derive(Debug, Clone, PartialEq)]
63
pub struct Brightness {
64
8
    proportion: Option<f64>,
65
}
66

            
67
/// Parameters for the `contrast()` filter function
68
///
69
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-contrast>
70
16
#[derive(Debug, Clone, PartialEq)]
71
pub struct Contrast {
72
8
    proportion: Option<f64>,
73
}
74

            
75
/// Parameters for the `drop-shadow()` filter function
76
///
77
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-drop-shadow>
78
22
#[derive(Debug, Clone, PartialEq)]
79
pub struct DropShadow {
80
11
    color: Option<Color>,
81
11
    dx: Option<Length<Horizontal>>,
82
11
    dy: Option<Length<Vertical>>,
83
11
    std_deviation: Option<ULength<Both>>,
84
}
85

            
86
/// Parameters for the `grayscale()` filter function
87
///
88
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-grayscale>
89
16
#[derive(Debug, Clone, PartialEq)]
90
pub struct Grayscale {
91
8
    proportion: Option<f64>,
92
}
93

            
94
/// Parameters for the `hue-rotate()` filter function
95
///
96
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-huerotate>
97
18
#[derive(Debug, Clone, PartialEq)]
98
pub struct HueRotate {
99
9
    angle: Option<Angle>,
100
}
101

            
102
/// Parameters for the `invert()` filter function
103
///
104
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-invert>
105
16
#[derive(Debug, Clone, PartialEq)]
106
pub struct Invert {
107
8
    proportion: Option<f64>,
108
}
109

            
110
/// Parameters for the `opacity()` filter function
111
///
112
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-opacity>
113
64
#[derive(Debug, Clone, PartialEq)]
114
pub struct Opacity {
115
32
    proportion: Option<f64>,
116
}
117

            
118
/// Parameters for the `saturate()` filter function
119
///
120
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-saturate>
121
16
#[derive(Debug, Clone, PartialEq)]
122
pub struct Saturate {
123
8
    proportion: Option<f64>,
124
}
125

            
126
/// Parameters for the `sepia()` filter function
127
///
128
/// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#funcdef-filter-sepia>
129
22
#[derive(Debug, Clone, PartialEq)]
130
pub struct Sepia {
131
11
    proportion: Option<f64>,
132
}
133

            
134
/// Reads an optional number or percentage from the parser.
135
/// Negative numbers are not allowed.
136
35
fn parse_num_or_percentage(parser: &mut Parser<'_, '_>) -> Option<f64> {
137
70
    match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
138
21
        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
139
20
        Ok(NumberOrPercentage { value }) => Some(value),
140
14
        Err(_) => None,
141
    }
142
35
}
143

            
144
/// Reads an optional number or percentage from the parser, returning a value clamped to [0, 1].
145
/// Negative numbers are not allowed.
146
23
fn parse_num_or_percentage_clamped(parser: &mut Parser<'_, '_>) -> Option<f64> {
147
37
    parse_num_or_percentage(parser).map(|value| value.clamp(0.0, 1.0))
148
23
}
149

            
150
3367
fn parse_function<'i, F>(
151
    parser: &mut Parser<'i, '_>,
152
    name: &str,
153
    f: F,
154
) -> Result<FilterFunction, ParseError<'i>>
155
where
156
    F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<FilterFunction, ParseError<'i>>,
157
{
158
3367
    parser.expect_function_matching(name)?;
159
57
    parser.parse_nested_block(f)
160
3367
}
161

            
162
// This function doesn't fail, but returns a Result like the other parsers, so tell Clippy
163
// about that.
164
#[allow(clippy::unnecessary_wraps)]
165
7
fn parse_blur<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
166
14
    let length = parser.try_parse(|p| Length::parse(p)).ok();
167

            
168
7
    Ok(FilterFunction::Blur(Blur {
169
        std_deviation: length,
170
    }))
171
7
}
172

            
173
#[allow(clippy::unnecessary_wraps)]
174
4
fn parse_brightness<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
175
4
    let proportion = parse_num_or_percentage(parser);
176

            
177
4
    Ok(FilterFunction::Brightness(Brightness { proportion }))
178
4
}
179

            
180
#[allow(clippy::unnecessary_wraps)]
181
4
fn parse_contrast<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
182
4
    let proportion = parse_num_or_percentage(parser);
183

            
184
4
    Ok(FilterFunction::Contrast(Contrast { proportion }))
185
4
}
186
#[allow(clippy::unnecessary_wraps)]
187
10
fn parse_dropshadow<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
188
10
    let mut result = DropShadow {
189
10
        color: None,
190
10
        dx: None,
191
10
        dy: None,
192
10
        std_deviation: None,
193
    };
194

            
195
10
    result.color = parser.try_parse(Color::parse).ok();
196

            
197
    // if dx is provided, dy must follow and an optional std_dev must follow that.
198
18
    if let Ok(dx) = parser.try_parse(Length::parse) {
199
10
        result.dx = Some(dx);
200
10
        result.dy = Some(parser.try_parse(Length::parse)?);
201
8
        result.std_deviation = parser.try_parse(ULength::parse).ok();
202
10
    }
203

            
204
8
    let loc = parser.current_source_location();
205

            
206
    // because the color and length arguments can be provided in either order,
207
    // check again after potentially parsing lengths if the color is now provided.
208
    // if a color is provided both before and after, that is an error.
209
10
    if let Ok(c) = parser.try_parse(Color::parse) {
210
3
        if result.color.is_some() {
211
1
            return Err(
212
1
                loc.new_custom_error(ValueErrorKind::Value("color already specified".to_string()))
213
            );
214
        } else {
215
2
            result.color = Some(c);
216
        }
217
8
    }
218

            
219
7
    Ok(FilterFunction::DropShadow(result))
220
10
}
221

            
222
#[allow(clippy::unnecessary_wraps)]
223
4
fn parse_grayscale<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
224
4
    let proportion = parse_num_or_percentage_clamped(parser);
225

            
226
4
    Ok(FilterFunction::Grayscale(Grayscale { proportion }))
227
4
}
228

            
229
#[allow(clippy::unnecessary_wraps)]
230
5
fn parse_huerotate<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
231
10
    let angle = parser.try_parse(|p| Angle::parse(p)).ok();
232

            
233
5
    Ok(FilterFunction::HueRotate(HueRotate { angle }))
234
5
}
235

            
236
#[allow(clippy::unnecessary_wraps)]
237
4
fn parse_invert<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
238
4
    let proportion = parse_num_or_percentage_clamped(parser);
239

            
240
4
    Ok(FilterFunction::Invert(Invert { proportion }))
241
4
}
242

            
243
#[allow(clippy::unnecessary_wraps)]
244
8
fn parse_opacity<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
245
8
    let proportion = parse_num_or_percentage_clamped(parser);
246

            
247
8
    Ok(FilterFunction::Opacity(Opacity { proportion }))
248
8
}
249

            
250
#[allow(clippy::unnecessary_wraps)]
251
4
fn parse_saturate<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
252
4
    let proportion = parse_num_or_percentage(parser);
253

            
254
4
    Ok(FilterFunction::Saturate(Saturate { proportion }))
255
4
}
256

            
257
#[allow(clippy::unnecessary_wraps)]
258
7
fn parse_sepia<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
259
7
    let proportion = parse_num_or_percentage_clamped(parser);
260

            
261
7
    Ok(FilterFunction::Sepia(Sepia { proportion }))
262
7
}
263

            
264
impl Blur {
265
3
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
266
        // The 0.0 default is from the spec
267
6
        let std_dev = self.std_deviation.map(|l| l.to_user(params)).unwrap_or(0.0);
268

            
269
3
        let user_space_filter = Filter::default().to_user_space(params);
270

            
271
3
        let gaussian_blur = ResolvedPrimitive {
272
3
            primitive: Primitive::default(),
273
3
            params: PrimitiveParams::GaussianBlur(GaussianBlur {
274
3
                std_deviation: NumberOptionalNumber(std_dev, std_dev),
275
3
                ..GaussianBlur::default()
276
            }),
277
        }
278
3
        .into_user_space(params);
279

            
280
3
        FilterSpec {
281
3
            name: "blur()".to_string(),
282
            user_space_filter,
283
3
            primitives: vec![gaussian_blur],
284
        }
285
3
    }
286
}
287

            
288
impl Brightness {
289
1
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
290
1
        let user_space_filter = Filter::default().to_user_space(params);
291
1
        let slope = self.proportion.unwrap_or(1.0);
292

            
293
1
        let brightness = ResolvedPrimitive {
294
1
            primitive: Primitive::default(),
295
1
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
296
1
                functions: component_transfer::Functions {
297
1
                    r: FeFuncR(FeFuncCommon {
298
1
                        function_type: component_transfer::FunctionType::Linear,
299
                        slope,
300
1
                        ..FeFuncCommon::default()
301
                    }),
302
1
                    g: FeFuncG(FeFuncCommon {
303
1
                        function_type: component_transfer::FunctionType::Linear,
304
                        slope,
305
1
                        ..FeFuncCommon::default()
306
                    }),
307
1
                    b: FeFuncB(FeFuncCommon {
308
1
                        function_type: component_transfer::FunctionType::Linear,
309
                        slope,
310
1
                        ..FeFuncCommon::default()
311
                    }),
312
1
                    a: FeFuncA::default(),
313
                },
314
1
                ..component_transfer::ComponentTransfer::default()
315
            }),
316
        }
317
1
        .into_user_space(params);
318

            
319
1
        FilterSpec {
320
1
            name: "brightness()".to_string(),
321
            user_space_filter,
322
1
            primitives: vec![brightness],
323
        }
324
1
    }
325
}
326

            
327
impl Contrast {
328
1
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
329
1
        let user_space_filter = Filter::default().to_user_space(params);
330
1
        let slope = self.proportion.unwrap_or(1.0);
331
1
        let intercept = -(0.5 * slope) + 0.5;
332

            
333
1
        let contrast = ResolvedPrimitive {
334
1
            primitive: Primitive::default(),
335
1
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
336
1
                functions: component_transfer::Functions {
337
1
                    r: FeFuncR(FeFuncCommon {
338
1
                        function_type: component_transfer::FunctionType::Linear,
339
                        slope,
340
                        intercept,
341
1
                        ..FeFuncCommon::default()
342
                    }),
343
1
                    g: FeFuncG(FeFuncCommon {
344
1
                        function_type: component_transfer::FunctionType::Linear,
345
                        slope,
346
                        intercept,
347
1
                        ..FeFuncCommon::default()
348
                    }),
349
1
                    b: FeFuncB(FeFuncCommon {
350
1
                        function_type: component_transfer::FunctionType::Linear,
351
                        slope,
352
                        intercept,
353
1
                        ..FeFuncCommon::default()
354
                    }),
355
1
                    a: FeFuncA::default(),
356
                },
357
1
                ..component_transfer::ComponentTransfer::default()
358
            }),
359
        }
360
1
        .into_user_space(params);
361

            
362
1
        FilterSpec {
363
1
            name: "contrast()".to_string(),
364
            user_space_filter,
365
1
            primitives: vec![contrast],
366
        }
367
1
    }
368
}
369

            
370
/// Creates the filter primitives required for a `feDropShadow` effect.
371
///
372
/// Both the `drop-shadow()` filter function and the `feDropShadow` element need to create
373
/// a sequence of filter primitives (blur, offset, etc.) to build the drop shadow.  This
374
/// function builds that sequence.
375
2
pub fn drop_shadow_primitives(
376
    dx: f64,
377
    dy: f64,
378
    std_deviation: NumberOptionalNumber<f64>,
379
    color: Color,
380
) -> Vec<ResolvedPrimitive> {
381
2
    let offsetblur = CustomIdent("offsetblur".to_string());
382

            
383
2
    let gaussian_blur = ResolvedPrimitive {
384
2
        primitive: Primitive::default(),
385
2
        params: PrimitiveParams::GaussianBlur(GaussianBlur {
386
2
            in1: Input::SourceAlpha,
387
            std_deviation,
388
2
            ..GaussianBlur::default()
389
        }),
390
2
    };
391

            
392
2
    let offset = ResolvedPrimitive {
393
2
        primitive: Primitive {
394
2
            result: Some(offsetblur.clone()),
395
2
            ..Primitive::default()
396
        },
397
2
        params: PrimitiveParams::Offset(Offset {
398
2
            in1: Input::default(),
399
            dx,
400
            dy,
401
        }),
402
2
    };
403

            
404
2
    let flood = ResolvedPrimitive {
405
2
        primitive: Primitive::default(),
406
2
        params: PrimitiveParams::Flood(Flood { color }),
407
    };
408

            
409
2
    let composite = ResolvedPrimitive {
410
2
        primitive: Primitive::default(),
411
2
        params: PrimitiveParams::Composite(Composite {
412
2
            in2: Input::FilterOutput(offsetblur),
413
2
            operator: Operator::In,
414
2
            ..Composite::default()
415
        }),
416
2
    };
417

            
418
2
    let merge = ResolvedPrimitive {
419
2
        primitive: Primitive::default(),
420
2
        params: PrimitiveParams::Merge(Merge {
421
4
            merge_nodes: vec![
422
2
                MergeNode::default(),
423
2
                MergeNode {
424
2
                    in1: Input::SourceGraphic,
425
2
                    ..MergeNode::default()
426
                },
427
            ],
428
        }),
429
2
    };
430

            
431
2
    vec![gaussian_blur, offset, flood, composite, merge]
432
2
}
433

            
434
impl DropShadow {
435
    /// Converts a DropShadow into the set of filter element primitives.
436
    ///
437
    /// See <https://www.w3.org/TR/filter-effects/#dropshadowEquivalent>.
438
1
    fn to_filter_spec(&self, params: &NormalizeParams, default_color: Color) -> FilterSpec {
439
1
        let user_space_filter = Filter::default().to_user_space(params);
440
2
        let dx = self.dx.map(|l| l.to_user(params)).unwrap_or(0.0);
441
2
        let dy = self.dy.map(|l| l.to_user(params)).unwrap_or(0.0);
442
2
        let std_dev = self.std_deviation.map(|l| l.to_user(params)).unwrap_or(0.0);
443
1
        let std_deviation = NumberOptionalNumber(std_dev, std_dev);
444
3
        let color = self
445
            .color
446
            .as_ref()
447
2
            .map(|c| resolve_color(c, UnitInterval::clamp(1.0), &default_color))
448
1
            .unwrap_or(default_color);
449

            
450
1
        let resolved_primitives = drop_shadow_primitives(dx, dy, std_deviation, color);
451

            
452
2
        let primitives = resolved_primitives
453
            .into_iter()
454
6
            .map(|p| p.into_user_space(params))
455
            .collect();
456

            
457
1
        FilterSpec {
458
1
            name: "drop-shadow()".to_string(),
459
            user_space_filter,
460
1
            primitives,
461
        }
462
1
    }
463
}
464

            
465
impl Grayscale {
466
1
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
467
        // grayscale is implemented as the inverse of a saturate operation,
468
        // with the input clamped to the range [0, 1] by the parser.
469
1
        let p = 1.0 - self.proportion.unwrap_or(1.0);
470
1
        let saturate = Saturate {
471
1
            proportion: Some(p),
472
        };
473

            
474
1
        let user_space_filter = Filter::default().to_user_space(params);
475
1
        let primitive = saturate.to_user_space_primitive(params);
476

            
477
1
        FilterSpec {
478
1
            name: "grayscale".to_string(),
479
            user_space_filter,
480
1
            primitives: vec![primitive],
481
        }
482
1
    }
483
}
484

            
485
impl HueRotate {
486
1
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
487
2
        let rads = self.angle.map(|a| a.radians()).unwrap_or(0.0);
488
1
        let user_space_filter = Filter::default().to_user_space(params);
489

            
490
1
        let huerotate = ResolvedPrimitive {
491
1
            primitive: Primitive::default(),
492
1
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
493
1
                matrix: ColorMatrix::hue_rotate_matrix(rads),
494
1
                ..ColorMatrix::default()
495
            }),
496
        }
497
1
        .into_user_space(params);
498

            
499
1
        FilterSpec {
500
1
            name: "hue-rotate".to_string(),
501
            user_space_filter,
502
1
            primitives: vec![huerotate],
503
        }
504
1
    }
505
}
506

            
507
impl Invert {
508
1
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
509
1
        let p = self.proportion.unwrap_or(1.0);
510
1
        let user_space_filter = Filter::default().to_user_space(params);
511

            
512
1
        let invert = ResolvedPrimitive {
513
1
            primitive: Primitive::default(),
514
1
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
515
1
                functions: component_transfer::Functions {
516
1
                    r: FeFuncR(FeFuncCommon {
517
1
                        function_type: component_transfer::FunctionType::Table,
518
1
                        table_values: vec![p, 1.0 - p],
519
1
                        ..FeFuncCommon::default()
520
                    }),
521
1
                    g: FeFuncG(FeFuncCommon {
522
1
                        function_type: component_transfer::FunctionType::Table,
523
1
                        table_values: vec![p, 1.0 - p],
524
1
                        ..FeFuncCommon::default()
525
                    }),
526
1
                    b: FeFuncB(FeFuncCommon {
527
1
                        function_type: component_transfer::FunctionType::Table,
528
1
                        table_values: vec![p, 1.0 - p],
529
1
                        ..FeFuncCommon::default()
530
                    }),
531
1
                    a: FeFuncA::default(),
532
                },
533
1
                ..component_transfer::ComponentTransfer::default()
534
            }),
535
        }
536
1
        .into_user_space(params);
537

            
538
1
        FilterSpec {
539
1
            name: "invert".to_string(),
540
            user_space_filter,
541
1
            primitives: vec![invert],
542
        }
543
1
    }
544
}
545

            
546
impl Opacity {
547
5
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
548
5
        let p = self.proportion.unwrap_or(1.0);
549
5
        let user_space_filter = Filter::default().to_user_space(params);
550

            
551
5
        let opacity = ResolvedPrimitive {
552
5
            primitive: Primitive::default(),
553
5
            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
554
5
                functions: component_transfer::Functions {
555
5
                    a: FeFuncA(FeFuncCommon {
556
5
                        function_type: component_transfer::FunctionType::Table,
557
5
                        table_values: vec![0.0, p],
558
5
                        ..FeFuncCommon::default()
559
                    }),
560
5
                    ..component_transfer::Functions::default()
561
                },
562
5
                ..component_transfer::ComponentTransfer::default()
563
            }),
564
        }
565
5
        .into_user_space(params);
566

            
567
5
        FilterSpec {
568
5
            name: "opacity".to_string(),
569
            user_space_filter,
570
5
            primitives: vec![opacity],
571
        }
572
5
    }
573
}
574

            
575
impl Saturate {
576
    #[rustfmt::skip]
577
2
    fn matrix(&self) -> nalgebra::Matrix5<f64> {
578
2
        let p = self.proportion.unwrap_or(1.0);
579

            
580
2
        nalgebra::Matrix5::new(
581
2
            0.213 + 0.787 * p, 0.715 - 0.715 * p, 0.072 - 0.072 * p, 0.0, 0.0,
582
2
            0.213 - 0.213 * p, 0.715 + 0.285 * p, 0.072 - 0.072 * p, 0.0, 0.0,
583
2
            0.213 - 0.213 * p, 0.715 - 0.715 * p, 0.072 + 0.928 * p, 0.0, 0.0,
584
            0.0,               0.0,               0.0,               1.0, 0.0,
585
            0.0,               0.0,               0.0,               0.0, 1.0,
586
        )
587
2
    }
588

            
589
2
    fn to_user_space_primitive(&self, params: &NormalizeParams) -> UserSpacePrimitive {
590
2
        ResolvedPrimitive {
591
2
            primitive: Primitive::default(),
592
2
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
593
2
                matrix: self.matrix(),
594
2
                ..ColorMatrix::default()
595
            }),
596
        }
597
        .into_user_space(params)
598
2
    }
599

            
600
1
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
601
1
        let user_space_filter = Filter::default().to_user_space(params);
602

            
603
1
        let saturate = self.to_user_space_primitive(params);
604

            
605
1
        FilterSpec {
606
1
            name: "saturate".to_string(),
607
            user_space_filter,
608
1
            primitives: vec![saturate],
609
        }
610
1
    }
611
}
612

            
613
impl Sepia {
614
    #[rustfmt::skip]
615
1
    fn matrix(&self) -> nalgebra::Matrix5<f64> {
616
1
        let p = self.proportion.unwrap_or(1.0);
617

            
618
1
        nalgebra::Matrix5::new(
619
1
            0.393 + 0.607 * (1.0 - p), 0.769 - 0.769 * (1.0 - p), 0.189 - 0.189 * (1.0 - p), 0.0, 0.0,
620
1
            0.349 - 0.349 * (1.0 - p), 0.686 + 0.314 * (1.0 - p), 0.168 - 0.168 * (1.0 - p), 0.0, 0.0,
621
1
            0.272 - 0.272 * (1.0 - p), 0.534 - 0.534 * (1.0 - p), 0.131 + 0.869 * (1.0 - p), 0.0, 0.0,
622
            0.0,                       0.0,                       0.0,                       1.0, 0.0,
623
            0.0,                       0.0,                       0.0,                       0.0, 1.0,
624
        )
625
1
    }
626

            
627
1
    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
628
1
        let user_space_filter = Filter::default().to_user_space(params);
629

            
630
1
        let sepia = ResolvedPrimitive {
631
1
            primitive: Primitive::default(),
632
1
            params: PrimitiveParams::ColorMatrix(ColorMatrix {
633
1
                matrix: self.matrix(),
634
1
                ..ColorMatrix::default()
635
            }),
636
        }
637
1
        .into_user_space(params);
638

            
639
1
        FilterSpec {
640
1
            name: "sepia".to_string(),
641
            user_space_filter,
642
1
            primitives: vec![sepia],
643
        }
644
1
    }
645
}
646

            
647
impl Parse for FilterFunction {
648
    #[allow(clippy::type_complexity)]
649
    #[rustfmt::skip]
650
6995
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, crate::error::ParseError<'i>> {
651
6995
        let loc = parser.current_source_location();
652
13990
        let fns: Vec<(&str, &dyn Fn(&mut Parser<'i, '_>) -> _)> = vec![
653
6995
            ("blur",        &parse_blur),
654
6995
            ("brightness",  &parse_brightness),
655
6995
            ("contrast",    &parse_contrast),
656
6995
            ("drop-shadow", &parse_dropshadow),
657
6995
            ("grayscale",   &parse_grayscale),
658
6995
            ("hue-rotate",  &parse_huerotate),
659
6995
            ("invert",      &parse_invert),
660
6995
            ("opacity",     &parse_opacity),
661
6995
            ("saturate",    &parse_saturate),
662
6995
            ("sepia",       &parse_sepia),
663
        ];
664

            
665
10401
        for (filter_name, parse_fn) in fns {
666
10088
            if let Ok(func) = parser.try_parse(|p| parse_function(p, filter_name, parse_fn)) {
667
43
                return Ok(func);
668
            }
669
3363
        }
670

            
671
312
        return Err(loc.new_custom_error(ValueErrorKind::parse_error("expected filter function")));
672
355
    }
673
}
674

            
675
impl FilterFunction {
676
    // If this function starts actually returning an Err, remove this Clippy exception:
677
    #[allow(clippy::unnecessary_wraps)]
678
16
    pub fn to_filter_spec(&self, params: &NormalizeParams, current_color: Color) -> FilterSpec {
679
16
        match self {
680
3
            FilterFunction::Blur(v) => v.to_filter_spec(params),
681
1
            FilterFunction::Brightness(v) => v.to_filter_spec(params),
682
1
            FilterFunction::Contrast(v) => v.to_filter_spec(params),
683
1
            FilterFunction::DropShadow(v) => v.to_filter_spec(params, current_color),
684
1
            FilterFunction::Grayscale(v) => v.to_filter_spec(params),
685
1
            FilterFunction::HueRotate(v) => v.to_filter_spec(params),
686
1
            FilterFunction::Invert(v) => v.to_filter_spec(params),
687
5
            FilterFunction::Opacity(v) => v.to_filter_spec(params),
688
1
            FilterFunction::Saturate(v) => v.to_filter_spec(params),
689
1
            FilterFunction::Sepia(v) => v.to_filter_spec(params),
690
        }
691
16
    }
692
}
693

            
694
#[cfg(test)]
695
mod tests {
696
    use cssparser::RGBA;
697

            
698
    use super::*;
699

            
700
    #[test]
701
2
    fn parses_blur() {
702
1
        assert_eq!(
703
1
            FilterFunction::parse_str("blur()").unwrap(),
704
            FilterFunction::Blur(Blur {
705
                std_deviation: None
706
            })
707
        );
708

            
709
1
        assert_eq!(
710
1
            FilterFunction::parse_str("blur(5px)").unwrap(),
711
1
            FilterFunction::Blur(Blur {
712
1
                std_deviation: Some(Length::new(5.0, LengthUnit::Px))
713
            })
714
        );
715
2
    }
716

            
717
    #[test]
718
2
    fn parses_brightness() {
719
1
        assert_eq!(
720
1
            FilterFunction::parse_str("brightness()").unwrap(),
721
            FilterFunction::Brightness(Brightness { proportion: None })
722
        );
723

            
724
1
        assert_eq!(
725
1
            FilterFunction::parse_str("brightness(50%)").unwrap(),
726
1
            FilterFunction::Brightness(Brightness {
727
1
                proportion: Some(0.50_f32.into()),
728
            })
729
        );
730
2
    }
731

            
732
    #[test]
733
2
    fn parses_contrast() {
734
1
        assert_eq!(
735
1
            FilterFunction::parse_str("contrast()").unwrap(),
736
            FilterFunction::Contrast(Contrast { proportion: None })
737
        );
738

            
739
1
        assert_eq!(
740
1
            FilterFunction::parse_str("contrast(50%)").unwrap(),
741
1
            FilterFunction::Contrast(Contrast {
742
1
                proportion: Some(0.50_f32.into()),
743
            })
744
        );
745
2
    }
746

            
747
    #[test]
748
2
    fn parses_dropshadow() {
749
1
        assert_eq!(
750
1
            FilterFunction::parse_str("drop-shadow(4px 5px)").unwrap(),
751
1
            FilterFunction::DropShadow(DropShadow {
752
1
                color: None,
753
1
                dx: Some(Length::new(4.0, LengthUnit::Px)),
754
1
                dy: Some(Length::new(5.0, LengthUnit::Px)),
755
1
                std_deviation: None,
756
            })
757
        );
758

            
759
1
        assert_eq!(
760
1
            FilterFunction::parse_str("drop-shadow(#ff0000 4px 5px 32px)").unwrap(),
761
1
            FilterFunction::DropShadow(DropShadow {
762
1
                color: Some(Color::Rgba(RGBA {
763
1
                    red: Some(255),
764
1
                    green: Some(0),
765
1
                    blue: Some(0),
766
1
                    alpha: Some(1.0)
767
                })),
768
1
                dx: Some(Length::new(4.0, LengthUnit::Px)),
769
1
                dy: Some(Length::new(5.0, LengthUnit::Px)),
770
1
                std_deviation: Some(ULength::new(32.0, LengthUnit::Px)),
771
            })
772
        );
773

            
774
1
        assert_eq!(
775
1
            FilterFunction::parse_str("drop-shadow(1px 2px blue)").unwrap(),
776
1
            FilterFunction::DropShadow(DropShadow {
777
1
                color: Some(Color::Rgba(RGBA {
778
1
                    red: Some(0),
779
1
                    green: Some(0),
780
1
                    blue: Some(255),
781
1
                    alpha: Some(1.0)
782
                })),
783
1
                dx: Some(Length::new(1.0, LengthUnit::Px)),
784
1
                dy: Some(Length::new(2.0, LengthUnit::Px)),
785
1
                std_deviation: None,
786
            })
787
        );
788

            
789
1
        assert_eq!(
790
1
            FilterFunction::parse_str("drop-shadow(1px 2px 3px currentColor)").unwrap(),
791
1
            FilterFunction::DropShadow(DropShadow {
792
1
                color: Some(Color::CurrentColor),
793
1
                dx: Some(Length::new(1.0, LengthUnit::Px)),
794
1
                dy: Some(Length::new(2.0, LengthUnit::Px)),
795
1
                std_deviation: Some(ULength::new(3.0, LengthUnit::Px)),
796
            })
797
        );
798

            
799
1
        assert_eq!(
800
1
            FilterFunction::parse_str("drop-shadow(1 2 3)").unwrap(),
801
1
            FilterFunction::DropShadow(DropShadow {
802
1
                color: None,
803
1
                dx: Some(Length::new(1.0, LengthUnit::Px)),
804
1
                dy: Some(Length::new(2.0, LengthUnit::Px)),
805
1
                std_deviation: Some(ULength::new(3.0, LengthUnit::Px)),
806
            })
807
        );
808
2
    }
809

            
810
    #[test]
811
2
    fn parses_grayscale() {
812
1
        assert_eq!(
813
1
            FilterFunction::parse_str("grayscale()").unwrap(),
814
            FilterFunction::Grayscale(Grayscale { proportion: None })
815
        );
816

            
817
1
        assert_eq!(
818
1
            FilterFunction::parse_str("grayscale(50%)").unwrap(),
819
1
            FilterFunction::Grayscale(Grayscale {
820
1
                proportion: Some(0.50_f32.into()),
821
            })
822
        );
823
2
    }
824

            
825
    #[test]
826
2
    fn parses_huerotate() {
827
1
        assert_eq!(
828
1
            FilterFunction::parse_str("hue-rotate()").unwrap(),
829
            FilterFunction::HueRotate(HueRotate { angle: None })
830
        );
831

            
832
1
        assert_eq!(
833
1
            FilterFunction::parse_str("hue-rotate(0)").unwrap(),
834
1
            FilterFunction::HueRotate(HueRotate {
835
1
                angle: Some(Angle::new(0.0))
836
            })
837
        );
838

            
839
1
        assert_eq!(
840
1
            FilterFunction::parse_str("hue-rotate(128deg)").unwrap(),
841
1
            FilterFunction::HueRotate(HueRotate {
842
1
                angle: Some(Angle::from_degrees(128.0))
843
            })
844
        );
845
2
    }
846

            
847
    #[test]
848
2
    fn parses_invert() {
849
1
        assert_eq!(
850
1
            FilterFunction::parse_str("invert()").unwrap(),
851
            FilterFunction::Invert(Invert { proportion: None })
852
        );
853

            
854
1
        assert_eq!(
855
1
            FilterFunction::parse_str("invert(50%)").unwrap(),
856
1
            FilterFunction::Invert(Invert {
857
1
                proportion: Some(0.50_f32.into()),
858
            })
859
        );
860
2
    }
861

            
862
    #[test]
863
2
    fn parses_opacity() {
864
1
        assert_eq!(
865
1
            FilterFunction::parse_str("opacity()").unwrap(),
866
            FilterFunction::Opacity(Opacity { proportion: None })
867
        );
868

            
869
1
        assert_eq!(
870
1
            FilterFunction::parse_str("opacity(50%)").unwrap(),
871
1
            FilterFunction::Opacity(Opacity {
872
1
                proportion: Some(0.50_f32.into()),
873
            })
874
        );
875
2
    }
876

            
877
    #[test]
878
2
    fn parses_saturate() {
879
1
        assert_eq!(
880
1
            FilterFunction::parse_str("saturate()").unwrap(),
881
            FilterFunction::Saturate(Saturate { proportion: None })
882
        );
883

            
884
1
        assert_eq!(
885
1
            FilterFunction::parse_str("saturate(50%)").unwrap(),
886
1
            FilterFunction::Saturate(Saturate {
887
1
                proportion: Some(0.50_f32.into()),
888
            })
889
        );
890
2
    }
891

            
892
    #[test]
893
2
    fn parses_sepia() {
894
1
        assert_eq!(
895
1
            FilterFunction::parse_str("sepia()").unwrap(),
896
            FilterFunction::Sepia(Sepia { proportion: None })
897
        );
898

            
899
1
        assert_eq!(
900
1
            FilterFunction::parse_str("sepia(80%)").unwrap(),
901
1
            FilterFunction::Sepia(Sepia {
902
1
                proportion: Some(0.80_f32.into())
903
            })
904
        );
905

            
906
1
        assert_eq!(
907
1
            FilterFunction::parse_str("sepia(0.52)").unwrap(),
908
1
            FilterFunction::Sepia(Sepia {
909
1
                proportion: Some(0.52_f32.into())
910
            })
911
        );
912

            
913
        // values > 1.0 should be clamped to 1.0
914
1
        assert_eq!(
915
1
            FilterFunction::parse_str("sepia(1.5)").unwrap(),
916
            FilterFunction::Sepia(Sepia {
917
                proportion: Some(1.0)
918
            })
919
        );
920

            
921
        // negative numbers are invalid.
922
1
        assert_eq!(
923
1
            FilterFunction::parse_str("sepia(-1)").unwrap(),
924
            FilterFunction::Sepia(Sepia { proportion: None }),
925
        );
926
2
    }
927

            
928
    #[test]
929
2
    fn invalid_blur_yields_error() {
930
1
        assert!(FilterFunction::parse_str("blur(foo)").is_err());
931
1
        assert!(FilterFunction::parse_str("blur(42 43)").is_err());
932
2
    }
933

            
934
    #[test]
935
2
    fn invalid_brightness_yields_error() {
936
1
        assert!(FilterFunction::parse_str("brightness(foo)").is_err());
937
2
    }
938

            
939
    #[test]
940
2
    fn invalid_contrast_yields_error() {
941
1
        assert!(FilterFunction::parse_str("contrast(foo)").is_err());
942
2
    }
943

            
944
    #[test]
945
2
    fn invalid_dropshadow_yields_error() {
946
1
        assert!(FilterFunction::parse_str("drop-shadow(blue 5px green)").is_err());
947
1
        assert!(FilterFunction::parse_str("drop-shadow(blue 5px 5px green)").is_err());
948
1
        assert!(FilterFunction::parse_str("drop-shadow(blue 1px)").is_err());
949
1
        assert!(FilterFunction::parse_str("drop-shadow(1 2 3 4 blue)").is_err());
950
2
    }
951

            
952
    #[test]
953
2
    fn invalid_grayscale_yields_error() {
954
1
        assert!(FilterFunction::parse_str("grayscale(foo)").is_err());
955
2
    }
956

            
957
    #[test]
958
2
    fn invalid_huerotate_yields_error() {
959
1
        assert!(FilterFunction::parse_str("hue-rotate(foo)").is_err());
960
2
    }
961

            
962
    #[test]
963
2
    fn invalid_invert_yields_error() {
964
1
        assert!(FilterFunction::parse_str("invert(foo)").is_err());
965
2
    }
966

            
967
    #[test]
968
2
    fn invalid_opacity_yields_error() {
969
1
        assert!(FilterFunction::parse_str("opacity(foo)").is_err());
970
2
    }
971

            
972
    #[test]
973
2
    fn invalid_saturate_yields_error() {
974
1
        assert!(FilterFunction::parse_str("saturate(foo)").is_err());
975
2
    }
976

            
977
    #[test]
978
2
    fn invalid_sepia_yields_error() {
979
1
        assert!(FilterFunction::parse_str("sepia(foo)").is_err());
980
2
    }
981
}