usvg/parser/
filter.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5//! A collection of SVG filters.
6
7use std::collections::HashSet;
8use std::str::FromStr;
9use std::sync::Arc;
10
11use strict_num::PositiveF32;
12use svgtypes::{AspectRatio, Length, LengthUnit as Unit};
13
14use crate::{
15    filter::{self, *},
16    ApproxZeroUlps, Color, Group, Node, NonEmptyString, NonZeroF32, NonZeroRect, Opacity, Size,
17    Units,
18};
19
20use super::converter::{self, SvgColorExt};
21use super::paint_server::{convert_units, resolve_number};
22use super::svgtree::{AId, EId, FromValue, SvgNode};
23use super::OptionLog;
24
25impl<'a, 'input: 'a> FromValue<'a, 'input> for filter::ColorInterpolation {
26    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
27        match value {
28            "sRGB" => Some(filter::ColorInterpolation::SRGB),
29            "linearRGB" => Some(filter::ColorInterpolation::LinearRGB),
30            _ => None,
31        }
32    }
33}
34
35pub(crate) fn convert(
36    node: SvgNode,
37    state: &converter::State,
38    object_bbox: Option<NonZeroRect>,
39    cache: &mut converter::Cache,
40) -> Result<Vec<Arc<Filter>>, ()> {
41    let value = match node.attribute::<&str>(AId::Filter) {
42        Some(v) => v,
43        None => return Ok(Vec::new()),
44    };
45
46    let mut has_invalid_urls = false;
47    let mut filters = Vec::new();
48
49    let create_base_filter_func =
50        |kind, filters: &mut Vec<Arc<Filter>>, cache: &mut converter::Cache| {
51            // Filter functions, unlike `filter` elements, do not have a filter region.
52            // We're currently do not support an unlimited region, so we simply use a fairly large one.
53            // This if far from ideal, but good for now.
54            // TODO: Should be fixed eventually.
55            let mut rect = match kind {
56                Kind::DropShadow(_) | Kind::GaussianBlur(_) => {
57                    NonZeroRect::from_xywh(-0.5, -0.5, 2.0, 2.0).unwrap()
58                }
59                _ => NonZeroRect::from_xywh(-0.1, -0.1, 1.2, 1.2).unwrap(),
60            };
61
62            let object_bbox = match object_bbox {
63                Some(v) => v,
64                None => {
65                    log::warn!(
66                        "Filter '{}' has an invalid region. Skipped.",
67                        node.element_id()
68                    );
69                    return;
70                }
71            };
72
73            rect = rect.bbox_transform(object_bbox);
74
75            filters.push(Arc::new(Filter {
76                id: cache.gen_filter_id(),
77                rect,
78                primitives: vec![Primitive {
79                    rect: rect,
80                    // Unlike `filter` elements, filter functions use sRGB colors by default.
81                    color_interpolation: ColorInterpolation::SRGB,
82                    result: "result".to_string(),
83                    kind,
84                }],
85            }));
86        };
87
88    for func in svgtypes::FilterValueListParser::from(value) {
89        let func = match func {
90            Ok(v) => v,
91            Err(e) => {
92                // Skip the whole attribute list on error.
93                log::warn!("Failed to parse a filter value cause {}. Skipping.", e);
94                return Ok(Vec::new());
95            }
96        };
97
98        match func {
99            svgtypes::FilterValue::Blur(std_dev) => create_base_filter_func(
100                convert_blur_function(node, std_dev, state),
101                &mut filters,
102                cache,
103            ),
104            svgtypes::FilterValue::DropShadow {
105                color,
106                dx,
107                dy,
108                std_dev,
109            } => create_base_filter_func(
110                convert_drop_shadow_function(node, color, dx, dy, std_dev, state),
111                &mut filters,
112                cache,
113            ),
114            svgtypes::FilterValue::Brightness(amount) => {
115                create_base_filter_func(convert_brightness_function(amount), &mut filters, cache)
116            }
117            svgtypes::FilterValue::Contrast(amount) => {
118                create_base_filter_func(convert_contrast_function(amount), &mut filters, cache)
119            }
120            svgtypes::FilterValue::Grayscale(amount) => {
121                create_base_filter_func(convert_grayscale_function(amount), &mut filters, cache)
122            }
123            svgtypes::FilterValue::HueRotate(angle) => {
124                create_base_filter_func(convert_hue_rotate_function(angle), &mut filters, cache)
125            }
126            svgtypes::FilterValue::Invert(amount) => {
127                create_base_filter_func(convert_invert_function(amount), &mut filters, cache)
128            }
129            svgtypes::FilterValue::Opacity(amount) => {
130                create_base_filter_func(convert_opacity_function(amount), &mut filters, cache)
131            }
132            svgtypes::FilterValue::Sepia(amount) => {
133                create_base_filter_func(convert_sepia_function(amount), &mut filters, cache)
134            }
135            svgtypes::FilterValue::Saturate(amount) => {
136                create_base_filter_func(convert_saturate_function(amount), &mut filters, cache)
137            }
138            svgtypes::FilterValue::Url(url) => {
139                if let Some(link) = node.document().element_by_id(url) {
140                    if let Ok(res) = convert_url(link, state, object_bbox, cache) {
141                        if let Some(f) = res {
142                            filters.push(f);
143                        }
144                    } else {
145                        has_invalid_urls = true;
146                    }
147                } else {
148                    has_invalid_urls = true;
149                }
150            }
151        }
152    }
153
154    // If a `filter` attribute had urls pointing to a missing elements
155    // and there are no valid filters at all - this is an error.
156    //
157    // Note that an invalid url is not an error in general.
158    if filters.is_empty() && has_invalid_urls {
159        return Err(());
160    }
161
162    Ok(filters)
163}
164
165fn convert_url(
166    node: SvgNode,
167    state: &converter::State,
168    object_bbox: Option<NonZeroRect>,
169    cache: &mut converter::Cache,
170) -> Result<Option<Arc<Filter>>, ()> {
171    let units = convert_units(node, AId::FilterUnits, Units::ObjectBoundingBox);
172    let primitive_units = convert_units(node, AId::PrimitiveUnits, Units::UserSpaceOnUse);
173
174    // Check if this element was already converted.
175    //
176    // Only `userSpaceOnUse` clipPaths can be shared,
177    // because `objectBoundingBox` one will be converted into user one
178    // and will become node-specific.
179    let cacheable = units == Units::UserSpaceOnUse && primitive_units == Units::UserSpaceOnUse;
180    if cacheable {
181        if let Some(filter) = cache.filters.get(node.element_id()) {
182            return Ok(Some(filter.clone()));
183        }
184    }
185
186    let rect = NonZeroRect::from_xywh(
187        resolve_number(
188            node,
189            AId::X,
190            units,
191            state,
192            Length::new(-10.0, Unit::Percent),
193        ),
194        resolve_number(
195            node,
196            AId::Y,
197            units,
198            state,
199            Length::new(-10.0, Unit::Percent),
200        ),
201        resolve_number(
202            node,
203            AId::Width,
204            units,
205            state,
206            Length::new(120.0, Unit::Percent),
207        ),
208        resolve_number(
209            node,
210            AId::Height,
211            units,
212            state,
213            Length::new(120.0, Unit::Percent),
214        ),
215    );
216
217    let mut rect = rect
218        .log_none(|| {
219            log::warn!(
220                "Filter '{}' has an invalid region. Skipped.",
221                node.element_id()
222            )
223        })
224        .ok_or(())?;
225
226    if units == Units::ObjectBoundingBox {
227        if let Some(object_bbox) = object_bbox {
228            rect = rect.bbox_transform(object_bbox);
229        } else {
230            log::warn!("Filters on zero-sized shapes are not allowed.");
231            return Err(());
232        }
233    }
234
235    let node_with_primitives = match find_filter_with_primitives(node) {
236        Some(v) => v,
237        None => return Err(()),
238    };
239    let primitives = collect_children(
240        &node_with_primitives,
241        primitive_units,
242        state,
243        object_bbox,
244        rect,
245        cache,
246    );
247    if primitives.is_empty() {
248        return Err(());
249    }
250
251    let mut id = NonEmptyString::new(node.element_id().to_string()).ok_or(())?;
252    // Generate ID only when we're parsing `objectBoundingBox` filter for the second time.
253    if !cacheable && cache.filters.contains_key(id.get()) {
254        id = cache.gen_filter_id();
255    }
256    let id_copy = id.get().to_string();
257
258    let filter = Arc::new(Filter {
259        id,
260        rect,
261        primitives,
262    });
263
264    cache.filters.insert(id_copy, filter.clone());
265
266    Ok(Some(filter))
267}
268
269fn find_filter_with_primitives<'a>(node: SvgNode<'a, 'a>) -> Option<SvgNode<'a, 'a>> {
270    for link in node.href_iter() {
271        if link.tag_name() != Some(EId::Filter) {
272            log::warn!(
273                "Filter '{}' cannot reference '{}' via 'xlink:href'.",
274                node.element_id(),
275                link.tag_name().unwrap()
276            );
277            return None;
278        }
279
280        if link.has_children() {
281            return Some(link);
282        }
283    }
284
285    None
286}
287
288struct FilterResults {
289    names: HashSet<String>,
290    idx: usize,
291}
292
293fn collect_children(
294    filter: &SvgNode,
295    units: Units,
296    state: &converter::State,
297    object_bbox: Option<NonZeroRect>,
298    filter_region: NonZeroRect,
299    cache: &mut converter::Cache,
300) -> Vec<Primitive> {
301    let mut primitives = Vec::new();
302
303    let mut results = FilterResults {
304        names: HashSet::new(),
305        idx: 1,
306    };
307
308    let scale = if units == Units::ObjectBoundingBox {
309        if let Some(object_bbox) = object_bbox {
310            object_bbox.size()
311        } else {
312            // No need to warn. Already checked.
313            return Vec::new();
314        }
315    } else {
316        Size::from_wh(1.0, 1.0).unwrap()
317    };
318
319    for child in filter.children() {
320        let tag_name = match child.tag_name() {
321            Some(v) => v,
322            None => continue,
323        };
324
325        let filter_subregion = match resolve_primitive_region(
326            child,
327            tag_name,
328            units,
329            state,
330            object_bbox,
331            filter_region,
332        ) {
333            Some(v) => v,
334            None => break,
335        };
336
337        let kind =
338            match tag_name {
339                EId::FeDropShadow => convert_drop_shadow(child, scale, &primitives),
340                EId::FeGaussianBlur => convert_gaussian_blur(child, scale, &primitives),
341                EId::FeOffset => convert_offset(child, scale, &primitives),
342                EId::FeBlend => convert_blend(child, &primitives),
343                EId::FeFlood => convert_flood(child),
344                EId::FeComposite => convert_composite(child, &primitives),
345                EId::FeMerge => convert_merge(child, &primitives),
346                EId::FeTile => convert_tile(child, &primitives),
347                EId::FeImage => convert_image(child, filter_subregion, state, cache),
348                EId::FeComponentTransfer => convert_component_transfer(child, &primitives),
349                EId::FeColorMatrix => convert_color_matrix(child, &primitives),
350                EId::FeConvolveMatrix => convert_convolve_matrix(child, &primitives)
351                    .unwrap_or_else(create_dummy_primitive),
352                EId::FeMorphology => convert_morphology(child, scale, &primitives),
353                EId::FeDisplacementMap => convert_displacement_map(child, scale, &primitives),
354                EId::FeTurbulence => convert_turbulence(child),
355                EId::FeDiffuseLighting => convert_diffuse_lighting(child, &primitives)
356                    .unwrap_or_else(create_dummy_primitive),
357                EId::FeSpecularLighting => convert_specular_lighting(child, &primitives)
358                    .unwrap_or_else(create_dummy_primitive),
359                tag_name => {
360                    log::warn!("'{}' is not a valid filter primitive. Skipped.", tag_name);
361                    continue;
362                }
363            };
364
365        let color_interpolation = child
366            .find_attribute(AId::ColorInterpolationFilters)
367            .unwrap_or_default();
368
369        primitives.push(Primitive {
370            rect: filter_subregion,
371            color_interpolation,
372            result: gen_result(child, &mut results),
373            kind,
374        });
375    }
376
377    // TODO: remove primitives which results are not used
378
379    primitives
380}
381
382// TODO: rewrite/simplify/explain/whatever
383fn resolve_primitive_region(
384    fe: SvgNode,
385    kind: EId,
386    units: Units,
387    state: &converter::State,
388    bbox: Option<NonZeroRect>,
389    filter_region: NonZeroRect,
390) -> Option<NonZeroRect> {
391    let x = fe.try_convert_length(AId::X, units, state);
392    let y = fe.try_convert_length(AId::Y, units, state);
393    let width = fe.try_convert_length(AId::Width, units, state);
394    let height = fe.try_convert_length(AId::Height, units, state);
395
396    let region = match kind {
397        EId::FeFlood | EId::FeImage => {
398            // `feImage` uses the object bbox.
399            if units == Units::ObjectBoundingBox {
400                let bbox = bbox?;
401
402                // TODO: wrong
403                // let ts_bbox = tiny_skia::Rect::new(ts.e, ts.f, ts.a, ts.d).unwrap();
404
405                let r = NonZeroRect::from_xywh(
406                    x.unwrap_or(0.0),
407                    y.unwrap_or(0.0),
408                    width.unwrap_or(1.0),
409                    height.unwrap_or(1.0),
410                )?;
411
412                return Some(r.bbox_transform(bbox));
413            } else {
414                filter_region
415            }
416        }
417        _ => filter_region,
418    };
419
420    // TODO: Wrong! Does not account rotate and skew.
421    if units == Units::ObjectBoundingBox {
422        let subregion_bbox = NonZeroRect::from_xywh(
423            x.unwrap_or(0.0),
424            y.unwrap_or(0.0),
425            width.unwrap_or(1.0),
426            height.unwrap_or(1.0),
427        )?;
428
429        Some(region.bbox_transform(subregion_bbox))
430    } else {
431        NonZeroRect::from_xywh(
432            x.unwrap_or(region.x()),
433            y.unwrap_or(region.y()),
434            width.unwrap_or(region.width()),
435            height.unwrap_or(region.height()),
436        )
437    }
438}
439
440// A malformed filter primitive usually should produce a transparent image.
441// But since `FilterKind` structs are designed to always be valid,
442// we are using `FeFlood` as fallback.
443#[inline(never)]
444pub(crate) fn create_dummy_primitive() -> Kind {
445    Kind::Flood(Flood {
446        color: Color::black(),
447        opacity: Opacity::ZERO,
448    })
449}
450
451#[inline(never)]
452fn resolve_input(node: SvgNode, aid: AId, primitives: &[Primitive]) -> Input {
453    match node.attribute(aid) {
454        Some(s) => {
455            let input = parse_in(s);
456
457            // If `in` references an unknown `result` than fallback
458            // to previous result or `SourceGraphic`.
459            if let Input::Reference(ref name) = input {
460                if !primitives.iter().any(|p| p.result == *name) {
461                    return if let Some(prev) = primitives.last() {
462                        Input::Reference(prev.result.clone())
463                    } else {
464                        Input::SourceGraphic
465                    };
466                }
467            }
468
469            input
470        }
471        None => {
472            if let Some(prev) = primitives.last() {
473                // If `in` is not set and this is not the first primitive
474                // than the input is a result of the previous primitive.
475                Input::Reference(prev.result.clone())
476            } else {
477                // If `in` is not set and this is the first primitive
478                // than the input is `SourceGraphic`.
479                Input::SourceGraphic
480            }
481        }
482    }
483}
484
485fn parse_in(s: &str) -> Input {
486    match s {
487        "SourceGraphic" => Input::SourceGraphic,
488        "SourceAlpha" => Input::SourceAlpha,
489        "BackgroundImage" | "BackgroundAlpha" | "FillPaint" | "StrokePaint" => {
490            log::warn!("{} filter input isn't supported and not planed.", s);
491            Input::SourceGraphic
492        }
493        _ => Input::Reference(s.to_string()),
494    }
495}
496
497fn gen_result(node: SvgNode, results: &mut FilterResults) -> String {
498    match node.attribute::<&str>(AId::Result) {
499        Some(s) => {
500            // Remember predefined result.
501            results.names.insert(s.to_string());
502            results.idx += 1;
503
504            s.to_string()
505        }
506        None => {
507            // Generate an unique name for `result`.
508            loop {
509                let name = format!("result{}", results.idx);
510                results.idx += 1;
511
512                if !results.names.contains(&name) {
513                    return name;
514                }
515            }
516        }
517    }
518}
519
520fn convert_blend(fe: SvgNode, primitives: &[Primitive]) -> Kind {
521    let mode = fe.attribute(AId::Mode).unwrap_or_default();
522    let input1 = resolve_input(fe, AId::In, primitives);
523    let input2 = resolve_input(fe, AId::In2, primitives);
524    Kind::Blend(Blend {
525        mode,
526        input1,
527        input2,
528    })
529}
530
531fn convert_color_matrix(fe: SvgNode, primitives: &[Primitive]) -> Kind {
532    let kind = convert_color_matrix_kind(fe).unwrap_or_default();
533    Kind::ColorMatrix(ColorMatrix {
534        input: resolve_input(fe, AId::In, primitives),
535        kind,
536    })
537}
538
539fn convert_color_matrix_kind(fe: SvgNode) -> Option<ColorMatrixKind> {
540    match fe.attribute(AId::Type) {
541        Some("saturate") => {
542            if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
543                if !list.is_empty() {
544                    let n = crate::f32_bound(0.0, list[0], 1.0);
545                    return Some(ColorMatrixKind::Saturate(PositiveF32::new(n).unwrap()));
546                } else {
547                    return Some(ColorMatrixKind::Saturate(PositiveF32::new(1.0).unwrap()));
548                }
549            }
550        }
551        Some("hueRotate") => {
552            if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
553                if !list.is_empty() {
554                    return Some(ColorMatrixKind::HueRotate(list[0]));
555                } else {
556                    return Some(ColorMatrixKind::HueRotate(0.0));
557                }
558            }
559        }
560        Some("luminanceToAlpha") => {
561            return Some(ColorMatrixKind::LuminanceToAlpha);
562        }
563        _ => {
564            // Fallback to `matrix`.
565            if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
566                if list.len() == 20 {
567                    return Some(ColorMatrixKind::Matrix(list));
568                }
569            }
570        }
571    }
572
573    None
574}
575
576fn convert_component_transfer(fe: SvgNode, primitives: &[Primitive]) -> Kind {
577    let mut kind = ComponentTransfer {
578        input: resolve_input(fe, AId::In, primitives),
579        func_r: TransferFunction::Identity,
580        func_g: TransferFunction::Identity,
581        func_b: TransferFunction::Identity,
582        func_a: TransferFunction::Identity,
583    };
584
585    for child in fe.children().filter(|n| n.is_element()) {
586        if let Some(func) = convert_transfer_function(child) {
587            match child.tag_name().unwrap() {
588                EId::FeFuncR => kind.func_r = func,
589                EId::FeFuncG => kind.func_g = func,
590                EId::FeFuncB => kind.func_b = func,
591                EId::FeFuncA => kind.func_a = func,
592                _ => {}
593            }
594        }
595    }
596
597    Kind::ComponentTransfer(kind)
598}
599
600fn convert_transfer_function(node: SvgNode) -> Option<TransferFunction> {
601    match node.attribute(AId::Type)? {
602        "identity" => Some(TransferFunction::Identity),
603        "table" => match node.attribute::<Vec<f32>>(AId::TableValues) {
604            Some(values) => Some(TransferFunction::Table(values)),
605            None => Some(TransferFunction::Table(Vec::new())),
606        },
607        "discrete" => match node.attribute::<Vec<f32>>(AId::TableValues) {
608            Some(values) => Some(TransferFunction::Discrete(values)),
609            None => Some(TransferFunction::Discrete(Vec::new())),
610        },
611        "linear" => Some(TransferFunction::Linear {
612            slope: node.attribute(AId::Slope).unwrap_or(1.0),
613            intercept: node.attribute(AId::Intercept).unwrap_or(0.0),
614        }),
615        "gamma" => Some(TransferFunction::Gamma {
616            amplitude: node.attribute(AId::Amplitude).unwrap_or(1.0),
617            exponent: node.attribute(AId::Exponent).unwrap_or(1.0),
618            offset: node.attribute(AId::Offset).unwrap_or(0.0),
619        }),
620        _ => None,
621    }
622}
623
624fn convert_composite(fe: SvgNode, primitives: &[Primitive]) -> Kind {
625    let operator = match fe.attribute(AId::Operator).unwrap_or("over") {
626        "in" => CompositeOperator::In,
627        "out" => CompositeOperator::Out,
628        "atop" => CompositeOperator::Atop,
629        "xor" => CompositeOperator::Xor,
630        "arithmetic" => CompositeOperator::Arithmetic {
631            k1: fe.attribute(AId::K1).unwrap_or(0.0),
632            k2: fe.attribute(AId::K2).unwrap_or(0.0),
633            k3: fe.attribute(AId::K3).unwrap_or(0.0),
634            k4: fe.attribute(AId::K4).unwrap_or(0.0),
635        },
636        _ => CompositeOperator::Over,
637    };
638
639    let input1 = resolve_input(fe, AId::In, primitives);
640    let input2 = resolve_input(fe, AId::In2, primitives);
641
642    Kind::Composite(Composite {
643        operator,
644        input1,
645        input2,
646    })
647}
648
649fn convert_convolve_matrix(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
650    fn parse_target(target: Option<f32>, order: u32) -> Option<u32> {
651        let default_target = (order as f32 / 2.0).floor() as u32;
652        let target = target.unwrap_or(default_target as f32) as i32;
653        if target < 0 || target >= order as i32 {
654            None
655        } else {
656            Some(target as u32)
657        }
658    }
659
660    let mut order_x = 3;
661    let mut order_y = 3;
662    if let Some(value) = fe.attribute::<&str>(AId::Order) {
663        let mut s = svgtypes::NumberListParser::from(value);
664        let x = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(3);
665        let y = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(x);
666        if x > 0 && y > 0 {
667            order_x = x as u32;
668            order_y = y as u32;
669        }
670    }
671
672    let mut matrix = Vec::new();
673    if let Some(list) = fe.attribute::<Vec<f32>>(AId::KernelMatrix) {
674        if list.len() == (order_x * order_y) as usize {
675            matrix = list;
676        }
677    }
678
679    let mut kernel_sum: f32 = matrix.iter().sum();
680    // Round up to prevent float precision issues.
681    kernel_sum = (kernel_sum * 1_000_000.0).round() / 1_000_000.0;
682    if kernel_sum.approx_zero_ulps(4) {
683        kernel_sum = 1.0;
684    }
685
686    let divisor = fe.attribute(AId::Divisor).unwrap_or(kernel_sum);
687    if divisor.approx_zero_ulps(4) {
688        return None;
689    }
690
691    let bias = fe.attribute(AId::Bias).unwrap_or(0.0);
692
693    let target_x = parse_target(fe.attribute(AId::TargetX), order_x)?;
694    let target_y = parse_target(fe.attribute(AId::TargetY), order_y)?;
695
696    let kernel_matrix = ConvolveMatrixData::new(target_x, target_y, order_x, order_y, matrix)?;
697
698    let edge_mode = match fe.attribute(AId::EdgeMode).unwrap_or("duplicate") {
699        "none" => EdgeMode::None,
700        "wrap" => EdgeMode::Wrap,
701        _ => EdgeMode::Duplicate,
702    };
703
704    let preserve_alpha = fe.attribute(AId::PreserveAlpha).unwrap_or("false") == "true";
705
706    Some(Kind::ConvolveMatrix(ConvolveMatrix {
707        input: resolve_input(fe, AId::In, primitives),
708        matrix: kernel_matrix,
709        divisor: NonZeroF32::new(divisor).unwrap(),
710        bias,
711        edge_mode,
712        preserve_alpha,
713    }))
714}
715
716fn convert_displacement_map(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
717    let parse_channel = |aid| match fe.attribute(aid).unwrap_or("A") {
718        "R" => ColorChannel::R,
719        "G" => ColorChannel::G,
720        "B" => ColorChannel::B,
721        _ => ColorChannel::A,
722    };
723
724    // TODO: should probably split scale to scale_x and scale_y,
725    //       but resvg doesn't support displacement map anyway...
726    let scale = (scale.width() + scale.height()) / 2.0;
727
728    Kind::DisplacementMap(DisplacementMap {
729        input1: resolve_input(fe, AId::In, primitives),
730        input2: resolve_input(fe, AId::In2, primitives),
731        scale: fe.attribute(AId::Scale).unwrap_or(0.0) * scale,
732        x_channel_selector: parse_channel(AId::XChannelSelector),
733        y_channel_selector: parse_channel(AId::YChannelSelector),
734    })
735}
736
737fn convert_drop_shadow(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
738    let (std_dev_x, std_dev_y) = convert_std_dev_attr(fe, scale, "2 2");
739
740    let (color, opacity) = fe
741        .attribute(AId::FloodColor)
742        .unwrap_or_else(svgtypes::Color::black)
743        .split_alpha();
744
745    let flood_opacity = fe
746        .attribute::<Opacity>(AId::FloodOpacity)
747        .unwrap_or(Opacity::ONE);
748
749    Kind::DropShadow(DropShadow {
750        input: resolve_input(fe, AId::In, primitives),
751        dx: fe.attribute(AId::Dx).unwrap_or(2.0) * scale.width(),
752        dy: fe.attribute(AId::Dy).unwrap_or(2.0) * scale.height(),
753        std_dev_x,
754        std_dev_y,
755        color,
756        opacity: opacity * flood_opacity,
757    })
758}
759
760fn convert_flood(fe: SvgNode) -> Kind {
761    let (color, opacity) = fe
762        .attribute(AId::FloodColor)
763        .unwrap_or_else(svgtypes::Color::black)
764        .split_alpha();
765
766    let flood_opacity = fe
767        .attribute::<Opacity>(AId::FloodOpacity)
768        .unwrap_or(Opacity::ONE);
769
770    Kind::Flood(Flood {
771        color,
772        opacity: opacity * flood_opacity,
773    })
774}
775
776fn convert_gaussian_blur(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
777    let (std_dev_x, std_dev_y) = convert_std_dev_attr(fe, scale, "0 0");
778    Kind::GaussianBlur(GaussianBlur {
779        input: resolve_input(fe, AId::In, primitives),
780        std_dev_x,
781        std_dev_y,
782    })
783}
784
785fn convert_std_dev_attr(fe: SvgNode, scale: Size, default: &str) -> (PositiveF32, PositiveF32) {
786    let text = fe.attribute(AId::StdDeviation).unwrap_or(default);
787    let mut parser = svgtypes::NumberListParser::from(text);
788
789    let n1 = parser.next().and_then(|n| n.ok());
790    let n2 = parser.next().and_then(|n| n.ok());
791    // `stdDeviation` must have no more than two values.
792    // Otherwise we should fallback to `0 0`.
793    let n3 = parser.next().and_then(|n| n.ok());
794
795    let (std_dev_x, std_dev_y) = match (n1, n2, n3) {
796        (Some(n1), Some(n2), None) => (n1, n2),
797        (Some(n1), None, None) => (n1, n1),
798        _ => (0.0, 0.0),
799    };
800
801    let std_dev_x = (std_dev_x as f32) * scale.width();
802    let std_dev_y = (std_dev_y as f32) * scale.height();
803
804    let std_dev_x = PositiveF32::new(std_dev_x as f32).unwrap_or(PositiveF32::ZERO);
805    let std_dev_y = PositiveF32::new(std_dev_y as f32).unwrap_or(PositiveF32::ZERO);
806
807    (std_dev_x, std_dev_y)
808}
809
810fn convert_image(
811    fe: SvgNode,
812    filter_subregion: NonZeroRect,
813    state: &converter::State,
814    cache: &mut converter::Cache,
815) -> Kind {
816    match convert_image_inner(fe, filter_subregion, state, cache) {
817        Some(kind) => kind,
818        None => create_dummy_primitive(),
819    }
820}
821
822fn convert_image_inner(
823    fe: SvgNode,
824    filter_subregion: NonZeroRect,
825    state: &converter::State,
826    cache: &mut converter::Cache,
827) -> Option<Kind> {
828    let rendering_mode = fe
829        .find_attribute(AId::ImageRendering)
830        .unwrap_or(state.opt.image_rendering);
831
832    if let Some(node) = fe.try_attribute::<SvgNode>(AId::Href) {
833        let mut state = state.clone();
834        state.fe_image_link = true;
835        let mut root = Group::empty();
836        super::converter::convert_element(node, &state, cache, &mut root);
837        return if root.has_children() {
838            root.calculate_bounding_boxes();
839            // Transfer node id from group's child to the group itself if needed.
840            if let Some(Node::Group(ref mut g)) = root.children.first_mut() {
841                if let Some(child2) = g.children.first_mut() {
842                    g.id = child2.id().to_string();
843                    match child2 {
844                        Node::Group(ref mut g2) => g2.id.clear(),
845                        Node::Path(ref mut path) => path.id.clear(),
846                        Node::Image(ref mut image) => image.id.clear(),
847                        Node::Text(ref mut text) => text.id.clear(),
848                    }
849                }
850            }
851
852            Some(Kind::Image(Image { root }))
853        } else {
854            None
855        };
856    }
857
858    let href = fe.try_attribute(AId::Href).log_none(|| {
859        log::warn!("The 'feImage' element lacks the 'xlink:href' attribute. Skipped.")
860    })?;
861    let img_data = super::image::get_href_data(href, state)?;
862    let actual_size = img_data.actual_size()?;
863
864    let aspect: AspectRatio = fe.attribute(AId::PreserveAspectRatio).unwrap_or_default();
865
866    let mut root = Group::empty();
867    super::image::convert_inner(
868        img_data,
869        cache.gen_image_id().take(),
870        true,
871        rendering_mode,
872        aspect,
873        actual_size,
874        filter_subregion.translate_to(0.0, 0.0)?,
875        cache,
876        &mut root,
877    );
878    root.calculate_bounding_boxes();
879
880    Some(Kind::Image(Image { root }))
881}
882
883fn convert_diffuse_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
884    let light_source = convert_light_source(fe)?;
885    Some(Kind::DiffuseLighting(DiffuseLighting {
886        input: resolve_input(fe, AId::In, primitives),
887        surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(1.0),
888        diffuse_constant: fe.attribute(AId::DiffuseConstant).unwrap_or(1.0),
889        lighting_color: convert_lighting_color(fe),
890        light_source,
891    }))
892}
893
894fn convert_specular_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
895    let light_source = convert_light_source(fe)?;
896
897    let specular_exponent = fe.attribute(AId::SpecularExponent).unwrap_or(1.0);
898    if !(1.0..=128.0).contains(&specular_exponent) {
899        // When exponent is out of range, the whole filter primitive should be ignored.
900        return None;
901    }
902
903    let specular_exponent = crate::f32_bound(1.0, specular_exponent, 128.0);
904
905    Some(Kind::SpecularLighting(SpecularLighting {
906        input: resolve_input(fe, AId::In, primitives),
907        surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(1.0),
908        specular_constant: fe.attribute(AId::SpecularConstant).unwrap_or(1.0),
909        specular_exponent,
910        lighting_color: convert_lighting_color(fe),
911        light_source,
912    }))
913}
914
915#[inline(never)]
916fn convert_lighting_color(node: SvgNode) -> Color {
917    // Color's alpha doesn't affect lighting-color. Simply skip it.
918    match node.attribute(AId::LightingColor) {
919        Some("currentColor") => {
920            node.find_attribute(AId::Color)
921                // Yes, a missing `currentColor` resolves to black and not white.
922                .unwrap_or(svgtypes::Color::black())
923                .split_alpha()
924                .0
925        }
926        Some(value) => {
927            if let Ok(c) = svgtypes::Color::from_str(value) {
928                c.split_alpha().0
929            } else {
930                log::warn!("Failed to parse lighting-color value: '{}'.", value);
931                Color::white()
932            }
933        }
934        _ => Color::white(),
935    }
936}
937
938#[inline(never)]
939fn convert_light_source(parent: SvgNode) -> Option<LightSource> {
940    let child = parent.children().find(|n| {
941        matches!(
942            n.tag_name(),
943            Some(EId::FeDistantLight) | Some(EId::FePointLight) | Some(EId::FeSpotLight)
944        )
945    })?;
946
947    match child.tag_name() {
948        Some(EId::FeDistantLight) => Some(LightSource::DistantLight(DistantLight {
949            azimuth: child.attribute(AId::Azimuth).unwrap_or(0.0),
950            elevation: child.attribute(AId::Elevation).unwrap_or(0.0),
951        })),
952        Some(EId::FePointLight) => Some(LightSource::PointLight(PointLight {
953            x: child.attribute(AId::X).unwrap_or(0.0),
954            y: child.attribute(AId::Y).unwrap_or(0.0),
955            z: child.attribute(AId::Z).unwrap_or(0.0),
956        })),
957        Some(EId::FeSpotLight) => {
958            let specular_exponent = child.attribute(AId::SpecularExponent).unwrap_or(1.0);
959            let specular_exponent = PositiveF32::new(specular_exponent)
960                .unwrap_or_else(|| PositiveF32::new(1.0).unwrap());
961
962            Some(LightSource::SpotLight(SpotLight {
963                x: child.attribute(AId::X).unwrap_or(0.0),
964                y: child.attribute(AId::Y).unwrap_or(0.0),
965                z: child.attribute(AId::Z).unwrap_or(0.0),
966                points_at_x: child.attribute(AId::PointsAtX).unwrap_or(0.0),
967                points_at_y: child.attribute(AId::PointsAtY).unwrap_or(0.0),
968                points_at_z: child.attribute(AId::PointsAtZ).unwrap_or(0.0),
969                specular_exponent,
970                limiting_cone_angle: child.attribute(AId::LimitingConeAngle),
971            }))
972        }
973        _ => None,
974    }
975}
976
977fn convert_merge(fe: SvgNode, primitives: &[Primitive]) -> Kind {
978    let mut inputs = Vec::new();
979    for child in fe.children() {
980        inputs.push(resolve_input(child, AId::In, primitives));
981    }
982
983    Kind::Merge(Merge { inputs })
984}
985
986fn convert_morphology(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
987    let operator = match fe.attribute(AId::Operator).unwrap_or("erode") {
988        "dilate" => MorphologyOperator::Dilate,
989        _ => MorphologyOperator::Erode,
990    };
991
992    let mut radius_x = PositiveF32::new(scale.width()).unwrap();
993    let mut radius_y = PositiveF32::new(scale.height()).unwrap();
994    if let Some(list) = fe.attribute::<Vec<f32>>(AId::Radius) {
995        let mut rx = 0.0;
996        let mut ry = 0.0;
997        if list.len() == 2 {
998            rx = list[0];
999            ry = list[1];
1000        } else if list.len() == 1 {
1001            rx = list[0];
1002            ry = list[0]; // The same as `rx`.
1003        }
1004
1005        if rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) {
1006            rx = 1.0;
1007            ry = 1.0;
1008        }
1009
1010        // If only one of the values is zero, reset it to 1.0
1011        // This is not specified in the spec, but this is how Chrome and Safari work.
1012        if rx.approx_zero_ulps(4) && !ry.approx_zero_ulps(4) {
1013            rx = 1.0;
1014        }
1015        if !rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) {
1016            ry = 1.0;
1017        }
1018
1019        // Both values must be positive.
1020        if rx.is_sign_positive() && ry.is_sign_positive() {
1021            radius_x = PositiveF32::new(rx * scale.width()).unwrap();
1022            radius_y = PositiveF32::new(ry * scale.height()).unwrap();
1023        }
1024    }
1025
1026    Kind::Morphology(Morphology {
1027        input: resolve_input(fe, AId::In, primitives),
1028        operator,
1029        radius_x,
1030        radius_y,
1031    })
1032}
1033
1034fn convert_offset(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
1035    Kind::Offset(Offset {
1036        input: resolve_input(fe, AId::In, primitives),
1037        dx: fe.attribute(AId::Dx).unwrap_or(0.0) * scale.width(),
1038        dy: fe.attribute(AId::Dy).unwrap_or(0.0) * scale.height(),
1039    })
1040}
1041
1042fn convert_tile(fe: SvgNode, primitives: &[Primitive]) -> Kind {
1043    Kind::Tile(Tile {
1044        input: resolve_input(fe, AId::In, primitives),
1045    })
1046}
1047
1048fn convert_turbulence(fe: SvgNode) -> Kind {
1049    let mut base_frequency_x = PositiveF32::ZERO;
1050    let mut base_frequency_y = PositiveF32::ZERO;
1051    if let Some(list) = fe.attribute::<Vec<f32>>(AId::BaseFrequency) {
1052        let mut x = 0.0;
1053        let mut y = 0.0;
1054        if list.len() == 2 {
1055            x = list[0];
1056            y = list[1];
1057        } else if list.len() == 1 {
1058            x = list[0];
1059            y = list[0]; // The same as `x`.
1060        }
1061
1062        if x.is_sign_positive() && y.is_sign_positive() {
1063            base_frequency_x = PositiveF32::new(x).unwrap();
1064            base_frequency_y = PositiveF32::new(y).unwrap();
1065        }
1066    }
1067
1068    let mut num_octaves = fe.attribute(AId::NumOctaves).unwrap_or(1.0);
1069    if num_octaves.is_sign_negative() {
1070        num_octaves = 0.0;
1071    }
1072
1073    let kind = match fe.attribute(AId::Type).unwrap_or("turbulence") {
1074        "fractalNoise" => TurbulenceKind::FractalNoise,
1075        _ => TurbulenceKind::Turbulence,
1076    };
1077
1078    Kind::Turbulence(Turbulence {
1079        base_frequency_x,
1080        base_frequency_y,
1081        num_octaves: num_octaves.round() as u32,
1082        seed: fe.attribute::<f32>(AId::Seed).unwrap_or(0.0).trunc() as i32,
1083        stitch_tiles: fe.attribute(AId::StitchTiles) == Some("stitch"),
1084        kind,
1085    })
1086}
1087
1088#[inline(never)]
1089fn convert_grayscale_function(amount: f64) -> Kind {
1090    let amount = amount.min(1.0) as f32;
1091    Kind::ColorMatrix(ColorMatrix {
1092        input: Input::SourceGraphic,
1093        kind: ColorMatrixKind::Matrix(vec![
1094            (0.2126 + 0.7874 * (1.0 - amount)),
1095            (0.7152 - 0.7152 * (1.0 - amount)),
1096            (0.0722 - 0.0722 * (1.0 - amount)),
1097            0.0,
1098            0.0,
1099            (0.2126 - 0.2126 * (1.0 - amount)),
1100            (0.7152 + 0.2848 * (1.0 - amount)),
1101            (0.0722 - 0.0722 * (1.0 - amount)),
1102            0.0,
1103            0.0,
1104            (0.2126 - 0.2126 * (1.0 - amount)),
1105            (0.7152 - 0.7152 * (1.0 - amount)),
1106            (0.0722 + 0.9278 * (1.0 - amount)),
1107            0.0,
1108            0.0,
1109            0.0,
1110            0.0,
1111            0.0,
1112            1.0,
1113            0.0,
1114        ]),
1115    })
1116}
1117
1118#[inline(never)]
1119fn convert_sepia_function(amount: f64) -> Kind {
1120    let amount = amount.min(1.0) as f32;
1121    Kind::ColorMatrix(ColorMatrix {
1122        input: Input::SourceGraphic,
1123        kind: ColorMatrixKind::Matrix(vec![
1124            (0.393 + 0.607 * (1.0 - amount)),
1125            (0.769 - 0.769 * (1.0 - amount)),
1126            (0.189 - 0.189 * (1.0 - amount)),
1127            0.0,
1128            0.0,
1129            (0.349 - 0.349 * (1.0 - amount)),
1130            (0.686 + 0.314 * (1.0 - amount)),
1131            (0.168 - 0.168 * (1.0 - amount)),
1132            0.0,
1133            0.0,
1134            (0.272 - 0.272 * (1.0 - amount)),
1135            (0.534 - 0.534 * (1.0 - amount)),
1136            (0.131 + 0.869 * (1.0 - amount)),
1137            0.0,
1138            0.0,
1139            0.0,
1140            0.0,
1141            0.0,
1142            1.0,
1143            0.0,
1144        ]),
1145    })
1146}
1147
1148#[inline(never)]
1149fn convert_saturate_function(amount: f64) -> Kind {
1150    let amount = PositiveF32::new(amount as f32).unwrap_or(PositiveF32::ZERO);
1151    Kind::ColorMatrix(ColorMatrix {
1152        input: Input::SourceGraphic,
1153        kind: ColorMatrixKind::Saturate(amount),
1154    })
1155}
1156
1157#[inline(never)]
1158fn convert_hue_rotate_function(amount: svgtypes::Angle) -> Kind {
1159    Kind::ColorMatrix(ColorMatrix {
1160        input: Input::SourceGraphic,
1161        kind: ColorMatrixKind::HueRotate(amount.to_degrees() as f32),
1162    })
1163}
1164
1165#[inline(never)]
1166fn convert_invert_function(amount: f64) -> Kind {
1167    let amount = amount.min(1.0) as f32;
1168    Kind::ComponentTransfer(ComponentTransfer {
1169        input: Input::SourceGraphic,
1170        func_r: TransferFunction::Table(vec![amount, 1.0 - amount]),
1171        func_g: TransferFunction::Table(vec![amount, 1.0 - amount]),
1172        func_b: TransferFunction::Table(vec![amount, 1.0 - amount]),
1173        func_a: TransferFunction::Identity,
1174    })
1175}
1176
1177#[inline(never)]
1178fn convert_opacity_function(amount: f64) -> Kind {
1179    let amount = amount.min(1.0) as f32;
1180    Kind::ComponentTransfer(ComponentTransfer {
1181        input: Input::SourceGraphic,
1182        func_r: TransferFunction::Identity,
1183        func_g: TransferFunction::Identity,
1184        func_b: TransferFunction::Identity,
1185        func_a: TransferFunction::Table(vec![0.0, amount]),
1186    })
1187}
1188
1189#[inline(never)]
1190fn convert_brightness_function(amount: f64) -> Kind {
1191    let amount = amount as f32;
1192    Kind::ComponentTransfer(ComponentTransfer {
1193        input: Input::SourceGraphic,
1194        func_r: TransferFunction::Linear {
1195            slope: amount,
1196            intercept: 0.0,
1197        },
1198        func_g: TransferFunction::Linear {
1199            slope: amount,
1200            intercept: 0.0,
1201        },
1202        func_b: TransferFunction::Linear {
1203            slope: amount,
1204            intercept: 0.0,
1205        },
1206        func_a: TransferFunction::Identity,
1207    })
1208}
1209
1210#[inline(never)]
1211fn convert_contrast_function(amount: f64) -> Kind {
1212    let amount = amount as f32;
1213    Kind::ComponentTransfer(ComponentTransfer {
1214        input: Input::SourceGraphic,
1215        func_r: TransferFunction::Linear {
1216            slope: amount,
1217            intercept: -(0.5 * amount) + 0.5,
1218        },
1219        func_g: TransferFunction::Linear {
1220            slope: amount,
1221            intercept: -(0.5 * amount) + 0.5,
1222        },
1223        func_b: TransferFunction::Linear {
1224            slope: amount,
1225            intercept: -(0.5 * amount) + 0.5,
1226        },
1227        func_a: TransferFunction::Identity,
1228    })
1229}
1230
1231#[inline(never)]
1232fn convert_blur_function(node: SvgNode, std_dev: Length, state: &converter::State) -> Kind {
1233    let std_dev = PositiveF32::new(super::units::convert_user_length(
1234        std_dev,
1235        node,
1236        AId::Dx,
1237        state,
1238    ))
1239    .unwrap_or(PositiveF32::ZERO);
1240    Kind::GaussianBlur(GaussianBlur {
1241        input: Input::SourceGraphic,
1242        std_dev_x: std_dev,
1243        std_dev_y: std_dev,
1244    })
1245}
1246
1247#[inline(never)]
1248fn convert_drop_shadow_function(
1249    node: SvgNode,
1250    color: Option<svgtypes::Color>,
1251    dx: Length,
1252    dy: Length,
1253    std_dev: Length,
1254    state: &converter::State,
1255) -> Kind {
1256    let std_dev = PositiveF32::new(super::units::convert_user_length(
1257        std_dev,
1258        node,
1259        AId::Dx,
1260        state,
1261    ))
1262    .unwrap_or(PositiveF32::ZERO);
1263
1264    let (color, opacity) = color
1265        .unwrap_or_else(|| {
1266            node.find_attribute(AId::Color)
1267                .unwrap_or_else(svgtypes::Color::black)
1268        })
1269        .split_alpha();
1270
1271    Kind::DropShadow(DropShadow {
1272        input: Input::SourceGraphic,
1273        dx: super::units::convert_user_length(dx, node, AId::Dx, state),
1274        dy: super::units::convert_user_length(dy, node, AId::Dy, state),
1275        std_dev_x: std_dev,
1276        std_dev_y: std_dev,
1277        color,
1278        opacity,
1279    })
1280}