usvg 0.2.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 tree::prelude::*;
use super::prelude::*;
use super::{
    fill,
    stroke,
};


pub fn convert(
    text_elem: &svgdom::Node,
    mut parent: tree::Node,
    rtree: &mut tree::Tree,
) {
    let attrs = text_elem.attributes();
    let text_node = parent.append_kind(tree::NodeKind::Text(tree::Text {
        id: text_elem.id().clone(),
        transform: attrs.get_transform(AId::Transform).unwrap_or_default(),
        rotate: attrs.get_number_list(AId::Rotate).cloned(),
    }));

    convert_chunks(text_elem, text_node, rtree);
}

fn convert_chunks(
    text_elem: &svgdom::Node,
    mut parent: tree::Node,
    rtree: &mut tree::Tree,
) {
    let ref root_attrs = text_elem.attributes();

    let mut chunk_node = parent.append_kind(tree::NodeKind::TextChunk(tree::TextChunk {
        x: root_attrs.get_number_list(AId::X).cloned(),
        y: root_attrs.get_number_list(AId::Y).cloned(),
        dx: root_attrs.get_number_list(AId::Dx).cloned(),
        dy: root_attrs.get_number_list(AId::Dy).cloned(),
        anchor: conv_text_anchor(root_attrs),
    }));

    for tspan in text_elem.children() {
        debug_assert!(tspan.is_tag_name(EId::Tspan));

        let text = if let Some(node) = tspan.first_child() {
            node.text().clone()
        } else {
            continue;
        };

        let ref attrs = tspan.attributes();
        let x = attrs.get_number_list(AId::X).cloned();
        let y = attrs.get_number_list(AId::Y).cloned();
        let dx = attrs.get_number_list(AId::Dx).cloned();
        let dy = attrs.get_number_list(AId::Dy).cloned();

        if x.is_some() || y.is_some() || dx.is_some() || dy.is_some() {
            if chunk_node.children().count() > 0 {
                // Create new if current text chunk has children.
                chunk_node = parent.append_kind(tree::NodeKind::TextChunk(tree::TextChunk {
                    x,
                    y,
                    dx,
                    dy,
                    anchor: conv_text_anchor(attrs),
                }));
            } else {
                // Update existing chunk.
                if let tree::NodeKind::TextChunk(ref mut d) = *chunk_node.borrow_mut() {
                    if x.is_some() { d.x = x; }
                    if y.is_some() { d.y = y; }
                    if dx.is_some() { d.dx = dx; }
                    if dy.is_some() { d.dy = dy; }
                    d.anchor = conv_text_anchor(attrs);
                }
            }
        }

        let fill = fill::convert(rtree, attrs, true);
        let stroke = stroke::convert(rtree, attrs, true);
        let decoration = conv_tspan_decoration2(rtree, text_elem, &tspan);
        chunk_node.append_kind(tree::NodeKind::TSpan(Box::new(tree::TSpan {
            fill,
            stroke,
            font: convert_font(attrs),
            decoration,
            text,
        })));
    }

    debug_assert!(chunk_node.children().count() > 0);
}

struct TextDecoTypes {
    has_underline: bool,
    has_overline: bool,
    has_line_through: bool,
}

// 'text-decoration' defined in the 'text' element
// should be generated by 'prepare_text_decoration'.
fn conv_text_decoration(node: &svgdom::Node) -> TextDecoTypes {
    debug_assert!(node.is_tag_name(EId::Text));

    let attrs = node.attributes();

    let text = attrs.get_str_or(AId::TextDecoration, "");

    TextDecoTypes {
        has_underline: text.contains("underline"),
        has_overline: text.contains("overline"),
        has_line_through: text.contains("linethrough"),
    }
}

// 'text-decoration' in 'tspan' does not depend on parent elements.
fn conv_tspan_decoration(tspan: &svgdom::Node) -> TextDecoTypes {
    debug_assert!(tspan.is_tag_name(EId::Tspan));

    let attrs = tspan.attributes();

    let has_attr = |decoration_id: &str| {
        if let Some(id) = attrs.get_str(AId::TextDecoration) {
            if id == decoration_id {
                return true;
            }
        }

        false
    };

    TextDecoTypes {
        has_underline: has_attr("underline"),
        has_overline: has_attr("overline"),
        has_line_through: has_attr("line-through"),
    }
}

fn conv_tspan_decoration2(
    rtree: &tree::Tree,
    node: &svgdom::Node,
    tspan: &svgdom::Node
) -> tree::TextDecoration {
    let text_dec = conv_text_decoration(node);
    let tspan_dec = conv_tspan_decoration(tspan);

    let gen_style = |in_tspan: bool, in_text: bool| {
        let n = if in_tspan {
            tspan.clone()
        } else if in_text {
            node.clone()
        } else {
            return None;
        };

        let ref attrs = n.attributes();
        let fill = fill::convert(rtree, attrs, true);
        let stroke = stroke::convert(rtree, attrs, true);

        Some(tree::TextDecorationStyle {
            fill,
            stroke,
        })
    };

    tree::TextDecoration {
        underline: gen_style(tspan_dec.has_underline, text_dec.has_underline),
        overline: gen_style(tspan_dec.has_overline, text_dec.has_overline),
        line_through: gen_style(tspan_dec.has_line_through, text_dec.has_line_through),
    }
}

fn conv_text_anchor(attrs: &svgdom::Attributes) -> tree::TextAnchor {
    let av = attrs.get_str_or(AId::TextAnchor, "start");

    match av {
        "start" => tree::TextAnchor::Start,
        "middle" => tree::TextAnchor::Middle,
        "end" => tree::TextAnchor::End,
        _ => tree::TextAnchor::Start,
    }
}

fn convert_font(attrs: &svgdom::Attributes) -> tree::Font {
    let style = attrs.get_str_or(AId::FontStyle, "normal");
    let style = match style {
        "normal" => tree::FontStyle::Normal,
        "italic" => tree::FontStyle::Italic,
        "oblique" => tree::FontStyle::Oblique,
        _ => tree::FontStyle::Normal,
    };

    let variant = attrs.get_str_or(AId::FontVariant, "normal");
    let variant = match variant {
        "normal" => tree::FontVariant::Normal,
        "small-caps" => tree::FontVariant::SmallCaps,
        _ => tree::FontVariant::Normal,
    };

    let weight = attrs.get_str_or(AId::FontWeight, "normal");
    let weight = match weight {
        "normal" => tree::FontWeight::W400,
        "bold" => tree::FontWeight::W700,
        "100" => tree::FontWeight::W100,
        "200" => tree::FontWeight::W200,
        "300" => tree::FontWeight::W300,
        "400" => tree::FontWeight::W400,
        "500" => tree::FontWeight::W500,
        "600" => tree::FontWeight::W600,
        "700" => tree::FontWeight::W700,
        "800" => tree::FontWeight::W800,
        "900" => tree::FontWeight::W900,
        "bolder" | "lighter" => {
            warn!("'bolder' and 'lighter' font-weight must be already resolved.");
            tree::FontWeight::W400
        }
        _ => tree::FontWeight::W400,
    };

    let stretch = attrs.get_str_or(AId::FontStretch, "normal");
    let stretch = match stretch {
        "normal" => tree::FontStretch::Normal,
        "wider" => tree::FontStretch::Wider,
        "narrower" => tree::FontStretch::Narrower,
        "ultra-condensed" => tree::FontStretch::UltraCondensed,
        "extra-condensed" => tree::FontStretch::ExtraCondensed,
        "condensed" => tree::FontStretch::Condensed,
        "semi-condensed" => tree::FontStretch::SemiCondensed,
        "semi-expanded" => tree::FontStretch::SemiExpanded,
        "expanded" => tree::FontStretch::Expanded,
        "extra-expanded" => tree::FontStretch::ExtraExpanded,
        "ultra-expanded" => tree::FontStretch::UltraExpanded,
        _ => tree::FontStretch::Normal,
    };

    // a-font-size-001.svg
    let size = attrs.get_number_or(AId::FontSize, ::DEFAULT_FONT_SIZE);
    debug_assert!(size > 0.0);

    let family = attrs.get_str_or(AId::FontFamily, ::DEFAULT_FONT_FAMILY).to_owned();

    tree::Font {
        family,
        size,
        style,
        variant,
        weight,
        stretch,
    }
}