usvg 0.4.0

An SVG simplification library.
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// external
use svgdom;

// self
use tree;
use super::prelude::*;


pub fn convert(
    node: &svgdom::Node,
    opt: &Options,
    tree: &mut tree::Tree,
) {
    let ref attrs = node.attributes();

    let rect = super::convert_rect(attrs);
    if !(rect.width > 0.0 && rect.height > 0.0) {
        warn!("Filter '{}' has an invalid region. Skipped.", node.id());
        return;
    }

    let children = try_opt!(collect_children(node, opt), ());

    tree.append_to_defs(
        tree::NodeKind::Filter(tree::Filter {
            id: node.id().clone(),
            units: super::convert_element_units(&attrs, AId::FilterUnits),
            primitive_units: super::convert_element_units(&attrs, AId::PrimitiveUnits),
            rect,
            children,
        })
    );
}

fn collect_children(
    filter: &svgdom::Node,
    opt: &Options,
) -> Option<Vec<tree::FilterPrimitive>> {
    let mut children = Vec::new();

    for child in filter.children() {
        let kind = match child.tag_id() {
            Some(EId::FeGaussianBlur) => {
                convert_fe_gaussian_blur(&child)
            }
            Some(EId::FeOffset) => {
                convert_fe_offset(&child)
            }
            Some(EId::FeBlend) => {
                convert_fe_blend(&child)
            }
            Some(EId::FeFlood) => {
                convert_fe_flood(&child)
            }
            Some(EId::FeComposite) => {
                convert_fe_composite(&child)
            }
            Some(EId::FeMerge) => {
                convert_fe_merge(&child)
            }
            Some(EId::FeTile) => {
                tree::FilterKind::FeTile
            }
            Some(EId::FeImage) => {
                convert_fe_image(&child, opt)
            }
            Some(_) => {
                warn!("Filter with '{}' child is not supported.", child.tag_name());
                continue;
            }
            None => continue,
        };

        if let Some(fe) = convert_primitive(&child, kind) {
            children.push(fe);
        }
    }

    if !children.is_empty() {
        Some(children)
    } else {
        None
    }
}

fn convert_primitive(
    fe: &svgdom::Node,
    kind: tree::FilterKind,
) -> Option<tree::FilterPrimitive> {
    let attrs = fe.attributes();

    let filter_input = if let Some(s) = attrs.get_str(AId::In) {
        Some(parse_in(s))
    } else {
        None
    };

    let color_interpolation = super::convert_color_interpolation(
        &attrs, AId::ColorInterpolationFilters, tree::ColorInterpolation::LinearRGB
    );

    Some(tree::FilterPrimitive {
        x: attrs.get_number(AId::X),
        y: attrs.get_number(AId::Y),
        width: attrs.get_number(AId::Width),
        height: attrs.get_number(AId::Height),
        color_interpolation,
        filter_input,
        filter_result: attrs.get_str(AId::Result).map(|s| s.to_string()),
        kind,
    })
}

fn convert_fe_gaussian_blur(fe: &svgdom::Node) -> tree::FilterKind {
    let attrs = fe.attributes();

    let std_dev_list = attrs.get_number_list(AId::StdDeviation).cloned();

    let (mut std_dev_x, mut std_dev_y) = match std_dev_list {
        Some(list) => {
            if list.len() == 1 {
                (list[0], list[0])
            } else if list.len() == 2 {
                (list[0], list[1])
            } else {
                (0.0, 0.0)
            }
        }
        None => {
            (0.0, 0.0)
        }
    };

    if std_dev_x.is_sign_negative() { std_dev_x = 0.0; }
    if std_dev_y.is_sign_negative() { std_dev_y = 0.0; }

    tree::FilterKind::FeGaussianBlur(tree::FeGaussianBlur {
        std_dev_x: std_dev_x.into(),
        std_dev_y: std_dev_y.into(),
    })
}

fn convert_fe_offset(fe: &svgdom::Node) -> tree::FilterKind {
    let attrs = fe.attributes();
    tree::FilterKind::FeOffset(tree::FeOffset {
        dx: attrs.get_number_or(AId::Dx, 0.0),
        dy: attrs.get_number_or(AId::Dy, 0.0),
    })
}

fn convert_fe_blend(fe: &svgdom::Node) -> tree::FilterKind {
    let attrs = fe.attributes();

    let mode = match attrs.get_str_or(AId::Mode, "normal") {
        "multiply"  => tree::FeBlendMode::Multiply,
        "screen"    => tree::FeBlendMode::Screen,
        "darken"    => tree::FeBlendMode::Darken,
        "lighten"   => tree::FeBlendMode::Lighten,
        _           => tree::FeBlendMode::Normal,
    };

    // TODO: when first?
    let filter_input2 = Some(parse_in(attrs.get_str_or(AId::In2, "SourceGraphic")));

    tree::FilterKind::FeBlend(tree::FeBlend {
        mode,
        filter_input2,
    })
}

fn convert_fe_flood(fe: &svgdom::Node) -> tree::FilterKind {
    let attrs = fe.attributes();

    let color = attrs.get_color(AId::FloodColor).unwrap_or(tree::Color::black());
    let opacity = f64_bound(0.0, attrs.get_number_or(AId::FloodOpacity, 1.0), 1.0);

    tree::FilterKind::FeFlood(tree::FeFlood {
        color,
        opacity: tree::Opacity::new(opacity),
    })
}

fn convert_fe_composite(fe: &svgdom::Node) -> tree::FilterKind {
    let attrs = fe.attributes();

    let operator = match attrs.get_str_or(AId::Operator, "over") {
        "in"            => tree::FeCompositeOperator::In,
        "out"           => tree::FeCompositeOperator::Out,
        "atop"          => tree::FeCompositeOperator::Atop,
        "xor"           => tree::FeCompositeOperator::Xor,
        "arithmetic"    => tree::FeCompositeOperator::Arithmetic,
        _               => tree::FeCompositeOperator::Over,
    };

    // TODO: when first?
    let filter_input2 = Some(parse_in(attrs.get_str_or(AId::In2, "SourceGraphic")));

    tree::FilterKind::FeComposite(tree::FeComposite {
        operator,
        filter_input2,
    })
}

fn convert_fe_merge(fe: &svgdom::Node) -> tree::FilterKind {
    let mut inputs = Vec::new();
    for child in fe.children() {
        let attrs = child.attributes();
        let filter_input = if let Some(s) = attrs.get_str(AId::In) {
            Some(parse_in(s))
        } else {
            None
        };

        inputs.push(filter_input);
    }

    tree::FilterKind::FeMerge(tree::FeMerge {
        inputs,
    })
}

fn convert_fe_image(
    fe: &svgdom::Node,
    opt: &Options,
) -> tree::FilterKind {
    let ref attrs = fe.attributes();

    let aspect = super::convert_aspect(attrs);

    let href = match attrs.get_value(AId::Href) {
        Some(&AValue::String(ref s)) => s,
        _ => {
            warn!("The 'feImage' element lacks the 'xlink:href' attribute. Skipped.");
            return tree::FilterKind::FeImage(tree::FeImage {
                aspect,
                data: tree::FeImageKind::None,
            });
        }
    };

    let (img_data, format) = match super::image::get_href_data(href, opt.path.as_ref()) {
        Some((data, format)) => (data, format),
        None => {
            return tree::FilterKind::FeImage(tree::FeImage {
                aspect,
                data: tree::FeImageKind::None,
            });
        }
    };


    tree::FilterKind::FeImage(tree::FeImage {
        aspect: super::convert_aspect(attrs),
        data: tree::FeImageKind::Image(img_data, format),
    })
}

fn parse_in(s: &str) -> tree::FilterInput {
    match s {
        "SourceGraphic"     => tree::FilterInput::SourceGraphic,
        "SourceAlpha"       => tree::FilterInput::SourceAlpha,
        "BackgroundImage"   => tree::FilterInput::BackgroundImage,
        "BackgroundAlpha"   => tree::FilterInput::BackgroundAlpha,
        "FillPaint"         => tree::FilterInput::FillPaint,
        "StrokePaint"       => tree::FilterInput::StrokePaint,
        _                   => tree::FilterInput::Reference(s.to_string())
    }
}