use svgdom::{
self,
ElementType,
FilterSvg,
};
use tree;
use tree::prelude::*;
use short::{
AId,
AValue,
EId,
};
use traits::{
GetDefsNode,
GetValue,
GetViewBox,
};
use geom::*;
use {
Options,
};
mod clippath;
mod fill;
mod filter;
mod gradient;
mod image;
mod mask;
mod path;
mod pattern;
mod shapes;
mod stroke;
mod text;
mod prelude {
pub use svgdom::{
AttributeType,
ElementType,
FilterSvg,
FilterSvgAttrs,
FilterSvgAttrsMut,
FuzzyEq,
FuzzyZero,
};
pub use geom::*;
pub use short::*;
pub use traits::*;
pub use Options;
}
pub fn convert_doc(
svg_doc: &svgdom::Document,
opt: &Options,
) -> tree::Tree {
let svg = if let Some(svg) = svg_doc.svg_element() {
svg
} else {
warn!("An invalid SVG structure. An empty tree will be produced.");
let svg_kind = tree::Svg {
size: Size::new(100.0, 100.0),
view_box: tree::ViewBox {
rect: (0.0, 0.0, 100.0, 100.0).into(),
aspect: tree::AspectRatio::default(),
},
};
return tree::Tree::create(svg_kind);
};
let size = get_img_size(&svg);
let view_box = {
let attrs = svg.attributes();
tree::ViewBox {
rect: get_view_box(&svg, size),
aspect: convert_aspect(&attrs),
}
};
let svg_kind = tree::Svg {
size,
view_box,
};
let mut tree = tree::Tree::create(svg_kind);
convert_ref_nodes(svg_doc, opt, &mut tree);
convert_nodes(&svg, &mut tree.root(), opt, &mut tree);
tree
}
fn convert_ref_nodes(
svg_doc: &svgdom::Document,
opt: &Options,
tree: &mut tree::Tree,
) {
let defs_elem = try_opt!(svg_doc.defs_element(), ());
let mut later_nodes = Vec::new();
for (id, node) in defs_elem.children().svg() {
if !node.is_referenced() {
continue;
}
match id {
EId::LinearGradient => {
gradient::convert_linear(&node, tree);
}
EId::RadialGradient => {
gradient::convert_radial(&node, tree);
}
EId::ClipPath => {
let new_node = clippath::convert(&node, tree);
later_nodes.push((node, new_node));
}
EId::Mask => {
if let Some(new_node) = mask::convert(&node, tree) {
later_nodes.push((node, new_node));
}
}
EId::Pattern => {
if let Some(new_node) = pattern::convert(&node, tree) {
later_nodes.push((node, new_node));
}
}
EId::Filter => {
filter::convert(&node, opt, tree);
}
EId::Symbol => {
}
_ => {
warn!("Unsupported element '{}'.", id);
}
}
}
for (node, mut new_node) in later_nodes {
if node.is_tag_name(EId::ClipPath) {
clippath::convert_children(&node, &mut new_node, opt, tree);
if !new_node.has_children() {
warn!("ClipPath '{}' has no children. Skipped.", node.id());
new_node.detach();
}
} else if node.is_tag_name(EId::Mask) {
convert_nodes(&node, &mut new_node, opt, tree);
if !new_node.has_children() {
warn!("Mask '{}' has no children. Skipped.", node.id());
new_node.detach();
}
} else if node.is_tag_name(EId::Pattern) {
convert_nodes(&node, &mut new_node.clone(), opt, tree);
if !new_node.has_children() {
warn!("Pattern '{}' has no children. Skipped.", node.id());
new_node.detach();
}
}
}
let mut fix_clip_path = true;
let mut rm_nodes = Vec::new();
while fix_clip_path {
fix_clip_path = false;
rm_nodes.clear();
for node in tree.defs().children() {
if let tree::NodeKind::ClipPath(ref cp) = *node.borrow() {
if let Some(ref id) = cp.clip_path {
let is_valid_id = tree.defs().children().any(|n| &*n.id() == id.as_str());
if !is_valid_id {
rm_nodes.push(node.clone());
fix_clip_path = true;
}
}
}
}
for node in &mut rm_nodes {
warn!("ClipPath '{}' has an invalid 'clip-path'. Skipped.", node.id());
node.detach();
}
}
}
pub(super) fn convert_nodes(
parent: &svgdom::Node,
parent_node: &mut tree::Node,
opt: &Options,
tree: &mut tree::Tree,
) {
for (id, node) in parent.children().svg() {
if node.is_referenced() {
continue;
}
match id {
EId::Title
| EId::Desc
| EId::Metadata
| EId::Defs
| EId::View => {
}
EId::G => {
let attrs = node.attributes();
let clip_path = match resolve_iri(&node, EId::ClipPath, AId::ClipPath, tree) {
IriResolveResult::Id(id) => Some(id),
IriResolveResult::Skip => continue,
IriResolveResult::None => None,
};
let mask = match resolve_iri(&node, EId::Mask, AId::Mask, tree) {
IriResolveResult::Id(id) => Some(id),
IriResolveResult::Skip => continue,
IriResolveResult::None => None,
};
let filter = match resolve_iri(&node, EId::Filter, AId::Filter, tree) {
IriResolveResult::Id(id) => Some(id),
IriResolveResult::Skip => continue,
IriResolveResult::None => None,
};
let has_filter = filter.is_some();
let ts = attrs.get_transform(AId::Transform).unwrap_or_default();
let opacity = attrs.get_number(AId::Opacity).map(|v| v.into());
let mut g_node = parent_node.append_kind(tree::NodeKind::Group(tree::Group {
id: node.id().clone(),
transform: ts,
opacity,
clip_path,
mask,
filter,
}));
convert_nodes(&node, &mut g_node, opt, tree);
if !g_node.has_children() && !has_filter {
g_node.detach();
}
}
EId::Line
| EId::Rect
| EId::Polyline
| EId::Polygon
| EId::Circle
| EId::Ellipse => {
if let Some(d) = shapes::convert(&node) {
path::convert(&node, d, parent_node.clone(), tree);
}
}
EId::Use
| EId::Switch
| EId::Svg => {
warn!("'{}' must be already resolved.", id);
}
EId::Path => {
let attrs = node.attributes();
if let Some(d) = attrs.get_path(AId::D) {
path::convert(&node, d.clone(), parent_node.clone(), tree);
}
}
EId::Text => {
text::convert(&node, opt, parent_node.clone(), tree);
}
EId::Image => {
image::convert(&node, opt, parent_node.clone());
}
_ => {
warn!("Unsupported element '{}'.", id);
}
}
}
}
enum IriResolveResult {
Id(String),
Skip,
None,
}
fn resolve_iri(node: &svgdom::Node, eid: EId, aid: AId, tree: &tree::Tree) -> IriResolveResult {
let attrs = node.attributes();
if let Some(&AValue::FuncLink(ref link)) = attrs.get_type(aid) {
if link.is_tag_name(eid) {
if let Some(node) = tree.defs_by_id(&link.id()) {
return IriResolveResult::Id(node.id().to_string());
} else {
return IriResolveResult::Skip;
}
}
}
IriResolveResult::None
}
fn get_img_size(svg: &svgdom::Node) -> Size {
let attrs = svg.attributes();
let w = attrs.get_number(AId::Width);
let h = attrs.get_number(AId::Height);
if let (Some(w), Some(h)) = (w, h) {
Size::new(w.round(), h.round())
} else {
warn!("Invalid SVG size. Reset to 100x100.");
Size::new(100.0, 100.0)
}
}
fn get_view_box(svg: &svgdom::Node, size: Size) -> Rect {
match svg.get_viewbox() {
Some(vb) => vb,
None => {
warn!("Invalid SVG viewBox. Reset to '0 0 {} {}'.", size.width, size.height);
size.to_rect(0.0, 0.0)
}
}
}
fn convert_element_units(attrs: &svgdom::Attributes, aid: AId) -> tree::Units {
match attrs.get_str(aid) {
Some("userSpaceOnUse") => tree::Units::UserSpaceOnUse,
Some("objectBoundingBox") => tree::Units::ObjectBoundingBox,
_ => {
warn!("{} must be already resolved.", aid);
tree::Units::UserSpaceOnUse
}
}
}
fn convert_rect(attrs: &svgdom::Attributes) -> Rect {
(
attrs.get_number_or(AId::X, 0.0),
attrs.get_number_or(AId::Y, 0.0),
attrs.get_number_or(AId::Width, 0.0),
attrs.get_number_or(AId::Height, 0.0),
).into()
}
fn convert_aspect(attrs: &svgdom::Attributes) -> tree::AspectRatio {
let ratio: Option<&tree::AspectRatio> = attrs.get_type(AId::PreserveAspectRatio);
match ratio {
Some(v) => *v,
None => {
tree::AspectRatio {
defer: false,
align: tree::Align::XMidYMid,
slice: false,
}
}
}
}
fn convert_visibility(attrs: &svgdom::Attributes) -> tree::Visibility {
let s = attrs.get_str_or(AId::Visibility, "visible");
match s {
"visible" => tree::Visibility::Visible,
"hidden" => tree::Visibility::Hidden,
"collapse" => tree::Visibility::Collapse,
_ => {
warn!("Invalid visibility value '{}'.", s);
tree::Visibility::Visible
}
}
}
fn convert_color_interpolation(
attrs: &svgdom::Attributes,
aid: AId,
default: tree::ColorInterpolation,
) -> tree::ColorInterpolation {
let s = attrs.get_str_or(aid, "auto");
match s {
"sRGB" => tree::ColorInterpolation::SRGB,
"linearRGB" => tree::ColorInterpolation::LinearRGB,
"auto" => default,
_ => {
warn!("Invalid color-interpolation value '{}'.", s);
default
}
}
}