usvg/parser/
filter.rs

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