//! Module for drawing SVG files to the screen or maybe a texture etc. pub mod style; use crate::math::Vec2; use raylib::drawing::RaylibDraw; use std::fs::File; use std::io::{self, Read}; use std::ops::Deref; use std::path::Path; use std::str::FromStr; use style::Style; use svgtypes::{Path as SVGPath, PathSegment}; use xmltree::{Element, XMLNode}; /// Find the first XML-Node with the given name. With depth, the maximum depth the /// algorithm will search to can be set. If it is set to `None`, the algorithm will search the /// entire sub-tree. Returns `None` if no such child can be found. Returns itself, in case the root /// node is already of the name given. pub fn find_first_node(root: Element, name: &str, depth: Option) -> Option { // The abort condition of this recursive function. If the element itself is of the required // name, return it. if root.name == name { return Some(root); } // Also abort, if the depth is reached. if depth == Some(0) { return None; } // Decrease the depth by one for all calls on the children, if it is set. let depth = match depth { Some(depth) => Some(depth - 1), None => None, }; // Recursively look for the element in this node's children. for child in root.children { // We only care for the elements, not for comments, cdata etc. if let XMLNode::Element(element) = child { if let Some(element) = find_first_node(element, name, depth) { return Some(element); } } } None } /// Read an svg file from the given path. On success, return the first graphics data that is read /// from this file. This can be used to draw an SVG. pub fn read_svg_file>(file: P) -> io::Result { let mut file = File::open(file)?; let mut data = String::new(); file.read_to_string(&mut data)?; let root: Element = match Element::parse(data.as_bytes()) { Ok(root) => root, Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)), }; match find_first_node(root, "g", None) { Some(graphics) => Ok(graphics), None => Err(io::Error::new( io::ErrorKind::InvalidData, "No graphics element in the file", )), } } /// Trait that indicates a struct is capable of drawing SVG-elements. pub trait DrawSVG { /// Draw the elements given by `svg_data` and all its children to the implementor, with the /// specified scale and translated by `position_px`. fn draw_svg(&mut self, svg_data: &Element, pixels_per_m: f32, position_px: Vec2); } impl DrawSVG for D where D: RaylibDraw, { fn draw_svg(&mut self, svg_data: &Element, pixels_per_m: f32, position_px: Vec2) { assert_eq!(&svg_data.name, "g"); // Go through all the graphics children and draw them one by one for child in &svg_data.children { if let XMLNode::Element(child) = child { match child.name.as_str() { "path" => draw_path(self, child, pixels_per_m, position_px), other => warn!("Unsupported SVG-Element {}", other), } } } } } // Helper functions to draw specific parts of the SVG file -------------------- fn draw_path( d: &mut impl RaylibDraw, path_data: &Element, pixels_per_m: f32, position_px: Vec2, ) { let style = if let Some(style_data) = path_data.attributes.get("style") { match Style::from_str(style_data) { Ok(style) => style, Err(err) => { warn!("Could not parse path style: {}", err); warn!("Using default style instead"); Style::default() } } } else { Style::default() }; let move_data = match path_data.attributes.get("d") { Some(d) => d, None => { error!("Unable to draw path, no move data found"); return; } }; let mut path: SVGPath = match move_data.parse() { Ok(mv) => mv, Err(err) => { error!( "Unable to draw path, move data not correctly formatted: {}", err ); return; } }; path.conv_to_absolute(); let mut current_pos: Vec2 = Vec2::new(0., 0.); for segment in path.deref() { match segment { PathSegment::MoveTo { x, y, .. } => { current_pos.x = *x as f32; current_pos.y = *y as f32; } PathSegment::LineTo { x, y, .. } => { d.draw_line_ex( current_pos * pixels_per_m / 1000. + position_px, Vec2::new(*x as f32, *y as f32) * pixels_per_m / 1000. + position_px, pixels_per_m * style.stroke_width / 1000., style.stroke, ); current_pos.x = *x as f32; current_pos.y = *y as f32; } PathSegment::HorizontalLineTo { x, .. } => { d.draw_line_ex( current_pos * pixels_per_m / 1000. + position_px, Vec2::new(*x as f32, current_pos.y) * pixels_per_m / 1000. + position_px, pixels_per_m * style.stroke_width / 1000., style.stroke, ); current_pos.x = *x as f32; } PathSegment::VerticalLineTo { y, .. } => { d.draw_line_ex( current_pos * pixels_per_m / 1000. + position_px, Vec2::new(current_pos.x, *y as f32) * pixels_per_m / 1000. + position_px, pixels_per_m * style.stroke_width / 1000., style.stroke, ); current_pos.y = *y as f32; } PathSegment::ClosePath { .. } => return, other => warn!("Ignoring unsupported {:?}", other), } } }